1 /* This file is part of GEGL editor -- a gtk frontend for GEGL
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 3 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, see <https://www.gnu.org/licenses/>.
15  *
16  * Copyright (C) 2019 Øyvind Kolås
17  */
18 #include <ctype.h>
19 #include <string.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <dirent.h>
26 #include <math.h>
27 #include <mrg.h>
28 #include <mrg-string.h>
29 #include <gegl.h>
30 #include <gexiv2/gexiv2.h>
31 #include <gegl-paramspecs.h>
32 #include <gegl-operation.h>
33 #include <gegl-audio-fragment.h>
34 #include "mrg-gegl.h"
35 #include "argvs.h"
36 #include "ui.h"
37 
entry_load(MrgEvent * event,void * data1,void * data2)38 static void entry_load (MrgEvent *event, void *data1, void *data2)
39 {
40   GeState *o = data1;
41   char *newpath;
42   if (o->rev)
43     argvs_eval ("save");
44 
45   o->entry_no = GPOINTER_TO_INT(data2);
46   newpath = get_item_path_no (o, o->entry_no);
47   g_free (o->path);
48   o->path = newpath;
49   ui_load_path (o);
50   mrg_event_stop_propagate (event);
51   mrg_queue_draw (event->mrg, NULL);
52 }
53 
on_viewer_motion(MrgEvent * e,void * data1,void * data2)54 static void on_viewer_motion (MrgEvent *e, void *data1, void *data2)
55 {
56   GeState *o = data1;
57   {
58     if (!o->show_controls)
59     {
60       o->show_controls = 1;
61       mrg_queue_draw (o->mrg, NULL);
62     }
63     if (o->controls_timeout)
64     {
65       mrg_remove_idle (o->mrg, o->controls_timeout);
66       o->controls_timeout = 0;
67     }
68     o->controls_timeout = mrg_add_timeout (o->mrg, 2000, ui_hide_controls_cb, o);
69   }
70 }
71 
72 
fade_thumbbar_cb(Mrg * mrg,void * data)73 static int fade_thumbbar_cb (Mrg *mrg, void *data)
74 {
75   GeState *o = data;
76   o->show_thumbbar = 1;
77   mrg_queue_draw (o->mrg, NULL);
78   return 0;
79 }
80 
on_thumbbar_motion(MrgEvent * e,void * data1,void * data2)81 static void on_thumbbar_motion (MrgEvent *e, void *data1, void *data2)
82 {
83   GeState *o = data1;
84   on_viewer_motion (e, data1, NULL);
85   {
86     o->show_thumbbar = 2;
87     if (o->thumbbar_timeout)
88     {
89       mrg_remove_idle (o->mrg, o->thumbbar_timeout);
90       o->thumbbar_timeout = 0;
91     }
92     o->thumbbar_timeout = mrg_add_timeout (o->mrg, 4000, fade_thumbbar_cb, o);
93   }
94 }
95 
draw_edit(Mrg * mrg,float x,float y,float w,float h)96 static void draw_edit (Mrg *mrg, float x, float y, float w, float h)
97 {
98   cairo_t *cr = mrg_cr (mrg);
99   cairo_new_path (cr);
100   cairo_arc (cr, x+0.5*w, y+0.5*h, h * .4, 0.0, G_PI * 2);
101 }
102 
draw_grid(Mrg * mrg,float x,float y,float w,float h)103 static void draw_grid (Mrg *mrg, float x, float y, float w, float h)
104 {
105   cairo_t *cr = mrg_cr (mrg);
106   cairo_new_path (cr);
107   cairo_rectangle (cr, 0.00 *w + x, 0.00 * h + y, 0.33 * w, 0.33 * h);
108   cairo_rectangle (cr, 0.66 *w + x, 0.00 * h + y, 0.33 * w, 0.33 * h);
109   cairo_rectangle (cr, 0.00 *w + x, 0.66 * h + y, 0.33 * w, 0.33 * h);
110   cairo_rectangle (cr, 0.66 *w + x, 0.66 * h + y, 0.33 * w, 0.33 * h);
111 }
112 
113 
draw_back(Mrg * mrg,float x,float y,float w,float h)114 static void draw_back (Mrg *mrg, float x, float y, float w, float h)
115 {
116   cairo_t *cr = mrg_cr (mrg);
117   cairo_new_path (cr);
118   cairo_new_path (cr);
119   cairo_move_to (cr, x+0.9*w, y+0.1*h);
120   cairo_line_to (cr, x+0.9*w, y+0.9*h);
121   cairo_line_to (cr, x+0.1*w, y+0.5*h);
122 }
123 
draw_forward(Mrg * mrg,float x,float y,float w,float h)124 static void draw_forward (Mrg *mrg, float x, float y, float w, float h)
125 {
126   cairo_t *cr = mrg_cr (mrg);
127   cairo_new_path (cr);
128   cairo_move_to (cr, x+0.1*w, y+0.1*h);
129   cairo_line_to (cr, x+0.1*w, y+0.9*h);
130   cairo_line_to (cr, x+0.9*w, y+0.5*h);
131 
132 }
133 
on_thumbbar_drag(MrgEvent * e,void * data1,void * data2)134 static void on_thumbbar_drag (MrgEvent *e, void *data1, void *data2)
135 {
136   static float pinch_coord[4][2] = {0,};
137   static int   pinch = 0;
138   static float orig_zoom = 1.0;
139 
140   GeState *o = data1;
141   //GeglNode *node = data2;
142 
143   on_viewer_motion (e, data1, data2);
144   if (e->type == MRG_DRAG_RELEASE)
145   {
146     pinch = 0;
147   } else if (e->type == MRG_DRAG_PRESS)
148   {
149     if (e->device_no == 5) /* 5 is second finger/touch point */
150     {
151       pinch_coord[1][0] = e->device_x;
152       pinch_coord[1][1] = e->device_y;
153       pinch_coord[2][0] = pinch_coord[0][0];
154       pinch_coord[2][1] = pinch_coord[0][1];
155       pinch_coord[3][0] = pinch_coord[1][0];
156       pinch_coord[3][1] = pinch_coord[1][1];
157       pinch = 1;
158       orig_zoom = o->graph_scale;
159     }
160     else if (e->device_no == 1 || e->device_no == 4) /* 1 is mouse pointer 4 is first finger */
161     {
162       pinch_coord[0][0] = e->device_x;
163       pinch_coord[0][1] = e->device_y;
164     }
165   } else if (e->type == MRG_DRAG_MOTION)
166   {
167     if (e->device_no == 1 || e->device_no == 4) /* 1 is mouse pointer 4 is first finger */
168     {
169       pinch_coord[0][0] = e->device_x;
170       pinch_coord[0][1] = e->device_y;
171     }
172     if (e->device_no == 5)
173     {
174       pinch_coord[1][0] = e->device_x;
175       pinch_coord[1][1] = e->device_y;
176     }
177 
178     if (pinch)
179     {
180       float orig_dist = hypotf ( pinch_coord[2][0]- pinch_coord[3][0],
181                                  pinch_coord[2][1]- pinch_coord[3][1]);
182       float dist = hypotf (pinch_coord[0][0] - pinch_coord[1][0],
183                            pinch_coord[0][1] - pinch_coord[1][1]);
184     {
185       float x, y;
186       float screen_cx = (pinch_coord[0][0] + pinch_coord[1][0])/2;
187       float screen_cy = (pinch_coord[0][1] + pinch_coord[1][1])/2;
188       //get_coords_graph (o, screen_cx, screen_cy, &x, &y);
189 
190       x = (o->thumbbar_pan_x + screen_cx) / o->thumbbar_scale;
191       y = (o->thumbbar_pan_y + screen_cy) / o->thumbbar_scale;
192 
193       o->thumbbar_scale = orig_zoom * (dist / orig_dist);
194 
195       o->thumbbar_pan_x = x * o->thumbbar_scale - screen_cx;
196       o->thumbbar_pan_y = y * o->thumbbar_scale - screen_cy;
197 
198       o->thumbbar_pan_x -= (e->delta_x )/2; /* doing half contribution of motion per finger */
199       o->thumbbar_pan_y -= (e->delta_y )/2; /* is simple and roughly right */
200     }
201 
202     }
203     else
204     {
205       if (e->device_no == 1 || e->device_no == 4)
206       {
207         o->thumbbar_pan_x -= (e->delta_x );
208         o->thumbbar_pan_y -= (e->delta_y );
209       }
210     }
211     mrg_queue_draw (e->mrg, NULL);
212   }
213   mrg_event_stop_propagate (e);
214 }
215 
216 
on_thumbbar_scroll(MrgEvent * event,void * data1,void * data2)217 static void on_thumbbar_scroll (MrgEvent *event, void *data1, void *data2)
218 {
219   GeState *o = data1;
220   on_viewer_motion (event, data1, NULL);
221   switch (event->scroll_direction)
222   {
223      case MRG_SCROLL_DIRECTION_DOWN:
224        o->thumbbar_scale /= 1.1;
225        if (o->thumbbar_scale < 0.2)
226          o->thumbbar_scale = 0.2;
227        break;
228      case MRG_SCROLL_DIRECTION_UP:
229        o->thumbbar_scale *= 1.1;
230        if (o->thumbbar_scale > 3)
231          o->thumbbar_scale = 3;
232        break;
233      default:
234        break;
235   }
236   mrg_queue_draw (event->mrg, NULL);
237   mrg_event_stop_propagate (event);
238 }
239 
draw_thumb_bar(GeState * o)240 static void draw_thumb_bar (GeState *o)
241 {
242   Mrg *mrg = o->mrg;
243   float width = mrg_width(mrg);
244   float height = mrg_height(mrg);
245   cairo_t *cr = mrg_cr (mrg);
246 
247   float dim = height * 0.15 * o->thumbbar_scale;
248   float padding = .025;
249   float opacity;
250 
251   cairo_save (cr);
252 
253   if (o->show_thumbbar > 1)
254   {
255      opacity = o->thumbbar_opacity * (1.0 - 0.14) + 0.14 * 1.0;
256      if (opacity < 0.99)
257        mrg_queue_draw (o->mrg, NULL);
258   }
259   else
260   {
261      opacity = o->thumbbar_opacity * (1.0 - 0.07) + 0.07 * 0.00;
262      if (opacity > 0.02)
263        mrg_queue_draw (o->mrg, NULL);
264   }
265   o->thumbbar_opacity = opacity;
266 
267   cairo_rectangle (cr, 0, height-dim, width, dim);
268   mrg_listen (mrg, MRG_DRAG, on_thumbbar_drag, o, NULL);
269   mrg_listen (mrg, MRG_SCROLL, on_thumbbar_scroll, o, NULL);
270   mrg_listen (mrg, MRG_DRAG, on_thumbbar_motion, o, NULL);
271   mrg_listen (mrg, MRG_MOTION, on_thumbbar_motion, o, NULL);
272   mrg_listen (mrg, MRG_SCROLL, on_thumbbar_motion, o, NULL);
273   cairo_new_path (cr);
274 
275   if (opacity > 0.01)
276   {
277     float x = mrg_width(mrg)/2-dim/2 - o->thumbbar_pan_x;
278     int entry_no = o->entry_no;
279     int entries = g_list_length (o->index) + g_list_length (o->paths);
280 
281     while (x < width && entry_no < entries)
282     {
283       char *upath = get_item_path_no (o, entry_no);
284       char *path = ui_suffix_path (upath);
285       char *thumbpath = ui_get_thumb_path (upath);
286       int w, h;
287 
288       if (
289          access (thumbpath, F_OK) == 0 &&
290          mrg_query_image (mrg, thumbpath, &w, &h))
291       {
292 
293         float wdim = dim, hdim = dim;
294         if (w > h) hdim = dim / (1.0 * w / h);
295         else       wdim = dim * (1.0 * w / h);
296 
297         if (w!=0 && h!=0)
298         {
299           cairo_rectangle (mrg_cr (mrg), x, height-dim, wdim, hdim);
300           if (entry_no == o->entry_no)
301           cairo_set_source_rgba (mrg_cr (mrg), 1,1,0,.7 * opacity);
302           else
303           cairo_set_source_rgba (mrg_cr (mrg), 1,1,1,.1 * opacity);
304           mrg_listen (mrg, MRG_TAP, entry_load, o, GINT_TO_POINTER(entry_no));
305           cairo_fill (mrg_cr (mrg));
306           mrg_image (mrg, x + dim * padding, height-dim*(1.0-padding),
307                      wdim * (1.0-padding*2), hdim *(1.0-padding*2), opacity, thumbpath, NULL, NULL);
308         }
309       }
310       else
311       {
312          if (access (thumbpath, F_OK) != 0) // only queue if does not exist,
313                                             // mrg/stb_image seem to suffer on some of our pngs
314          {
315            ui_queue_thumb (upath);
316          }
317       }
318       x += dim;
319       g_free (thumbpath);
320       g_free (path);
321       g_free (upath);
322       entry_no ++;
323     }
324     x = mrg_width(mrg)/2-dim/2 - o->thumbbar_pan_x;
325     dim = height * 0.15 * o->thumbbar_scale;
326     x -= dim;
327     entry_no = o->entry_no-1;
328     while (x > -dim && entry_no >= 0)
329     {
330       char *upath = get_item_path_no (o, entry_no);
331       char *path = ui_suffix_path (upath);
332       char *thumbpath = ui_get_thumb_path (upath);
333       int w, h;
334 
335       if (
336          access (thumbpath, F_OK) == 0 &&
337          mrg_query_image (mrg, thumbpath, &w, &h))
338       {
339 
340         float wdim = dim, hdim = dim;
341         if (w > h) hdim = dim / (1.0 * w / h);
342         else       wdim = dim * (1.0 * w / h);
343 
344         if (w!=0 && h!=0)
345         {
346           cairo_rectangle (mrg_cr (mrg), x, height-dim, wdim, hdim);
347           if (entry_no == o->entry_no)
348           cairo_set_source_rgba (mrg_cr (mrg), 1,1,0,.7 * opacity);
349           else
350           cairo_set_source_rgba (mrg_cr (mrg), 1,1,1,.1 * opacity);
351           mrg_listen (mrg, MRG_TAP, entry_load, o, GINT_TO_POINTER(entry_no));
352           cairo_fill (mrg_cr (mrg));
353           mrg_image (mrg, x + dim * padding, height-dim*(1.0-padding),
354                      wdim * (1.0-padding*2), hdim *(1.0-padding*2), opacity, thumbpath, NULL, NULL);
355         }
356       }
357       else
358       {
359          if (access (thumbpath, F_OK) != 0) // only queue if does not exist,
360          {
361            ui_queue_thumb (upath);
362          }
363       }
364       x -= dim;
365       g_free (thumbpath);
366       g_free (path);
367       g_free (upath);
368       entry_no --;
369     }
370   }
371   cairo_restore (cr);
372 }
373 
on_timeline_drag(MrgEvent * e,void * data1,void * data2)374 static void on_timeline_drag (MrgEvent *e, void *data1, void *data2)
375 {
376   //static float pinch_coord[4][2] = {0,};
377   //static int   pinch = 0;
378   //static float orig_zoom = 1.0;
379   GeState *o = data1;
380   //float pos = o->pos;
381   float end = o->duration;
382 
383 
384   on_viewer_motion (e, data1, data2);
385 
386   if (e->type == MRG_DRAG_RELEASE)
387   {
388     //pinch = 0;
389   } else if (e->type == MRG_DRAG_PRESS)
390   {
391   } else if (e->type == MRG_DRAG_MOTION)
392   {
393   }
394 
395   set_clip_position (o, e->x / mrg_width (o->mrg) * end);
396 
397   mrg_event_stop_propagate (e);
398 }
399 
draw_timeline(GeState * o)400 static void draw_timeline (GeState *o)
401 {
402   Mrg *mrg = o->mrg;
403   float width = mrg_width(mrg);
404   float height = mrg_height(mrg);
405   cairo_t *cr = mrg_cr (mrg);
406   float pos = o->pos;
407   float end = o->duration;
408 
409   cairo_save (cr);
410   cairo_set_line_width (cr, 2);
411   cairo_new_path (cr);
412   cairo_rectangle (cr, 0, height * .9, width, height * .1);
413   cairo_set_source_rgba (cr, 1,1,1,.5);
414   mrg_listen (mrg, MRG_DRAG, on_timeline_drag, o, NULL);
415 
416   cairo_fill (cr);
417 
418   cairo_set_source_rgba (cr, 1,0,0,1);
419   cairo_rectangle (cr, width * pos/end, height * .9, 2, height * .1);
420   cairo_fill (cr);
421 
422   cairo_restore (cr);
423 }
424 
ui_viewer(GeState * o)425 void ui_viewer (GeState *o)
426 {
427   Mrg *mrg = o->mrg;
428   float width = mrg_width(mrg);
429   float height = mrg_height(mrg);
430   cairo_t *cr = mrg_cr (mrg);
431   cairo_save (cr);
432   cairo_rectangle (cr, 0,0, width, height);
433 
434   draw_grid (mrg, height * 0.1/4, height * 0.1/4, height * 0.10, height * 0.10);
435   if (o->show_controls)
436     ui_contrasty_stroke (cr);
437   else
438     cairo_new_path (cr);
439   cairo_rectangle (cr, 0, 0, height * 0.15, height * 0.15);
440   if (o->show_controls)
441   {
442     cairo_set_source_rgba (cr, 1,1,1,.1);
443     cairo_fill_preserve (cr);
444   }
445   mrg_listen (mrg, MRG_PRESS, ui_run_command, "parent", NULL);
446 
447   draw_back (mrg, height * .1 / 4, height * .5, height * .1, height *.1);
448   cairo_close_path (cr);
449   if (o->show_controls)
450     ui_contrasty_stroke (cr);
451   else
452     cairo_new_path (cr);
453   cairo_rectangle (cr, 0, height * .3, height * .15, height *.7);
454   if (o->show_controls)
455   {
456     cairo_set_source_rgba (cr, 1,1,1,.1);
457     cairo_fill_preserve (cr);
458   }
459   mrg_listen (mrg, MRG_TAP, ui_run_command, "prev", NULL);
460   cairo_new_path (cr);
461 
462   draw_forward (mrg, width - height * .12, height * .5, height * .1, height *.1);
463   cairo_close_path (cr);
464   if (o->show_controls)
465     ui_contrasty_stroke (cr);
466   else
467     cairo_new_path (cr);
468   cairo_rectangle (cr, width - height * .15, height * .3, height * .15, height *.7);
469 
470   if (o->show_controls)
471   {
472     cairo_set_source_rgba (cr, 1,1,1,.1);
473     cairo_fill_preserve (cr);
474   }
475   mrg_listen (mrg, MRG_TAP, ui_run_command, "next", NULL);
476   draw_edit (mrg, width - height * .15, height * .0, height * .15, height *.15);
477 
478   if (o->show_controls)
479     ui_contrasty_stroke (cr);
480   else
481     cairo_new_path (cr);
482   cairo_rectangle (cr, width - height * .15, height * .0, height * .15, height *.15);
483   if (o->show_controls)
484   {
485     cairo_set_source_rgba (cr, 1,1,1,.1);
486     cairo_fill_preserve (cr);
487   }
488   mrg_listen (mrg, MRG_PRESS, ui_run_command, "toggle editing", NULL);
489   cairo_new_path (cr);
490 
491   if (o->show_thumbbar)
492     draw_thumb_bar (o);
493 
494   if (o->is_video && o->show_controls)
495   {
496     draw_timeline (o);
497   }
498 
499 
500  cairo_restore (cr);
501 
502  mrg_add_binding (mrg, "control-s", NULL, NULL, ui_run_command, "toggle slideshow");
503 
504  if (o->is_fit)
505   {
506     mrg_add_binding (mrg, "right", NULL, "next image", ui_run_command, "next");
507     mrg_add_binding (mrg, "left", NULL, "previous image",  ui_run_command, "prev");
508   }
509 
510  mrg_add_binding (mrg, "page-down", NULL, NULL, ui_run_command, "next");
511  mrg_add_binding (mrg, "page-up", NULL, NULL,  ui_run_command, "prev");
512 
513  mrg_add_binding (mrg, "alt-right", NULL, "next image", ui_run_command, "next");
514  mrg_add_binding (mrg, "alt-left", NULL, "previous image",  ui_run_command, "prev");
515 
516  if (o->commandline[0]==0)
517  {
518    mrg_add_binding (mrg, "+", NULL, NULL, ui_run_command, "zoom in");
519    mrg_add_binding (mrg, "=", NULL, NULL, ui_run_command, "zoom in");
520    mrg_add_binding (mrg, "-", NULL, NULL, ui_run_command, "zoom out");
521    mrg_add_binding (mrg, "8", NULL, "pixel for pixel", ui_run_command, "zoom 1.0");
522    mrg_add_binding (mrg, "9", NULL, NULL, ui_run_command, "zoom fit");
523    mrg_add_binding (mrg, "0", NULL, NULL, ui_run_command, "star 0");
524    mrg_add_binding (mrg, "1", NULL, NULL, ui_run_command, "star 1");
525    mrg_add_binding (mrg, "2", NULL, NULL, ui_run_command, "star 2");
526    mrg_add_binding (mrg, "3", NULL, NULL, ui_run_command, "star 3");
527    mrg_add_binding (mrg, "4", NULL, NULL, ui_run_command, "star 4");
528    mrg_add_binding (mrg, "5", NULL, NULL, ui_run_command, "star 5");
529  }
530 
531   mrg_add_binding (mrg, "control-m", NULL, NULL, ui_run_command, "toggle mipmap");
532   mrg_add_binding (mrg, "control-y", NULL, NULL, ui_run_command, "toggle colormanaged-display");
533   mrg_add_binding (mrg, "control-delete", NULL, NULL,  ui_run_command, "discard");
534 }
535