1 /*
2  * marker-sketcher-window.c
3  *
4  * Copyright (C) 2017 - 2018 Marker Project
5  *
6  * Marker is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public License as
8  * published by the Free Software Foundation; either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * Marker 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 GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with Marker; see the file LICENSE.md. If not,
18  * see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 
23 #include "marker-sketcher-window.h"
24 
25 #include <math.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #define P_SIZE_S 3
30 #define P_SIZE_M 6
31 #define P_SIZE_L 12
32 
33 #define F_SIZE_S 12
34 #define F_SIZE_M 14
35 #define F_SIZE_L 18
36 
37 struct _MarkerSketcherWindow
38 {
39   GtkWindow        parent_instance;
40 
41   GtkPopover* text_popover;
42   GtkEntry* text_entry;
43   GdkCursor* cursor;
44 
45   GtkDrawingArea* drawing_area;
46   cairo_surface_t*  surface;
47 
48   GList* history;
49   GList* future;
50 
51   gboolean status;
52   gdouble pos_x;
53   gdouble pos_y;
54 
55   gdouble size;
56   SketchTool tool;
57   GdkRGBA color;
58 
59   char * base_file;
60   MarkerSourceView * source_view;
61 };
62 
63 G_DEFINE_TYPE(MarkerSketcherWindow, marker_sketcher_window, GTK_TYPE_WINDOW)
64 
65 static char noname[11] = "Untitled.md";
66 
clean_surface_list(GList * list)67 void clean_surface_list(GList * list)
68 {
69   if (list->next)
70   {
71     clean_surface_list(list->next);
72   }
73   cairo_surface_destroy(list->data);
74 }
75 
76 static void
clear_surface(cairo_surface_t * surface)77 clear_surface (cairo_surface_t * surface)
78 {
79   cairo_t *cr;
80 
81   cr = cairo_create (surface);
82 
83   cairo_set_source_rgba (cr, 1, 1, 1, 1);
84   cairo_paint (cr);
85 
86   cairo_destroy (cr);
87 }
88 
89 
90 static gboolean
configure_event_cb(GtkWidget * widget,GdkEventConfigure * event,gpointer data)91 configure_event_cb (GtkWidget         *widget,
92                     GdkEventConfigure *event,
93                     gpointer           data)
94 {
95   MarkerSketcherWindow * w = MARKER_SKETCHER_WINDOW(data);
96   if (w->surface)
97   {
98     cairo_surface_t * destination = gdk_window_create_similar_surface (gtk_widget_get_window(widget),
99                                                                         CAIRO_CONTENT_COLOR_ALPHA,
100                                                                         gtk_widget_get_allocated_width (widget),
101                                                                         gtk_widget_get_allocated_height (widget));
102     clear_surface(destination);
103     cairo_t *cr = cairo_create (destination);
104     cairo_set_source_surface (cr, w->surface, 0, 0);
105     cairo_paint (cr);
106     cairo_surface_destroy(w->surface);
107     w->surface = destination;
108   }
109   else{
110     w->surface = gdk_window_create_similar_surface (gtk_widget_get_window(widget),
111                                                     CAIRO_CONTENT_COLOR_ALPHA,
112                                                     gtk_widget_get_allocated_width (widget),
113                                                     gtk_widget_get_allocated_height (widget));
114 
115     clear_surface (w->surface);
116   }
117   return TRUE;
118 }
119 
120 
121 static gboolean
draw_cb(GtkWidget * widget,cairo_t * cr,gpointer data)122 draw_cb (GtkWidget *widget,
123          cairo_t   *cr,
124          gpointer   data)
125 {
126   MarkerSketcherWindow * w = MARKER_SKETCHER_WINDOW(data);
127   cairo_set_source_surface (cr, w->surface, 0, 0);
128   cairo_paint (cr);
129   return FALSE;
130 }
131 
132 
133 static void
draw_text(MarkerSketcherWindow * w)134 draw_text(MarkerSketcherWindow *w)
135 {
136   GtkWidget* widget = GTK_WIDGET(w->drawing_area);
137   gdouble x = w->pos_x;
138   gdouble y = w->pos_y;
139   const gchar * text = gtk_entry_get_text(w->text_entry);
140   if (!widget || !w->surface)
141   {
142     return;
143   }
144 
145   cairo_t *cr;
146 
147   cr = cairo_create (w->surface);
148   if (w->size == P_SIZE_S)
149     cairo_set_font_size(cr, F_SIZE_S);
150   else if (w->size == P_SIZE_M)
151     cairo_set_font_size(cr, F_SIZE_M);
152   else
153     cairo_set_font_size(cr, F_SIZE_L);
154   cairo_set_source_rgb(cr, w->color.red, w->color.green, w->color.blue);
155   cairo_move_to(cr, x, y);
156   cairo_show_text(cr, text);
157   cairo_destroy(cr);
158   gtk_widget_queue_draw(widget);
159 }
160 
161 
162 static void
draw_brush(GtkWidget * widget,gdouble x,gdouble y,MarkerSketcherWindow * w)163 draw_brush (GtkWidget *widget,
164             gdouble    x,
165             gdouble    y,
166             MarkerSketcherWindow * w)
167 {
168   if (!widget || !w->surface)
169   {
170     return;
171   }
172 
173   cairo_t *cr;
174   cr = cairo_create (w->surface);
175   if (w->tool == PEN)
176     cairo_set_source_rgba(cr, w->color.red, w->color.green, w->color.blue, w->color.alpha);
177   else if (w->tool == ERASER)
178     cairo_set_source_rgb(cr, 1, 1, 1);
179   if (!w->status)
180   {
181 
182     cairo_arc(cr, x, y, w->size/2, 0, 2.0*M_PI) ;
183     cairo_fill (cr);
184 
185     cairo_destroy (cr);
186     gtk_widget_queue_draw(widget); //, x - size, y - size, 2*size, 2*size);
187   } else
188   {
189     cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
190 
191     cairo_set_line_width(cr, w->size);
192     cairo_move_to(cr, w->pos_x, w->pos_y);
193     cairo_line_to(cr, x, y);
194     cairo_stroke(cr);
195     gdouble l = x > w->pos_x ? w->pos_x : x;
196     gdouble t = y > w->pos_y ? w->pos_y : y;
197 
198     cairo_destroy(cr);
199     gtk_widget_queue_draw_area (widget, l - w->size, t - w->size, fabs(x-w->pos_x) + 2*w->size, fabs(y-w->pos_y) + 2*w->size);
200 
201   }
202   w->status = TRUE;
203 
204   w->pos_x = x;
205   w->pos_y = y;
206 }
207 
208 static void
add_history(MarkerSketcherWindow * w)209 add_history(MarkerSketcherWindow * w)
210 {
211   GtkWidget * widget = GTK_WIDGET(w->drawing_area);
212   cairo_surface_t * destination = cairo_surface_create_similar(w->surface,
213                                                                CAIRO_CONTENT_COLOR_ALPHA,
214                                                                gtk_widget_get_allocated_width (widget),
215                                                                gtk_widget_get_allocated_height (widget));
216   cairo_t *cr = cairo_create (destination);
217   cairo_set_source_surface (cr, w->surface, 0, 0);
218   cairo_paint (cr);
219 
220   w->history = g_list_append(w->history, destination);
221   if (w->future)
222   {
223     clean_surface_list(w->future);
224     g_list_free(w->future);
225     w->future = NULL;
226   }
227 }
228 
229 static gboolean
button_press_event_cb(GtkWidget * widget,GdkEventButton * event,gpointer data)230 button_press_event_cb (GtkWidget      *widget,
231                        GdkEventButton *event,
232                        gpointer        data)
233 {
234   MarkerSketcherWindow * w = MARKER_SKETCHER_WINDOW(data);
235   /* paranoia check, in case we haven't gotten a configure event */
236   if (w->surface == NULL)
237     return FALSE;
238 
239   if (event->button == GDK_BUTTON_PRIMARY)
240   {
241     if (w->tool == PEN || w->tool == ERASER){
242 
243       if (!w->status)
244       {
245         add_history(w);
246       }
247       draw_brush (widget, event->x, event->y, w);
248     } else {
249       GdkRectangle rect;
250       rect.x = event->x;
251       rect.y = event->y;
252       rect.width = 1;
253       rect.height = 1;
254       w->pos_x = event->x;
255       w->pos_y = event->y;
256       gtk_popover_set_pointing_to(w->text_popover, &rect);
257       gtk_popover_popup(w->text_popover);
258     }
259   }
260 
261   return TRUE;
262 }
263 
264 static gboolean
button_release_event_cb(GtkWidget * widget,GdkEventButton * event,gpointer data)265 button_release_event_cb (GtkWidget      *widget,
266                        GdkEventButton *event,
267                        gpointer        data)
268 {
269 
270   if (event->button == GDK_BUTTON_PRIMARY)
271   {
272     MarkerSketcherWindow * w = MARKER_SKETCHER_WINDOW(data);
273     w->status = FALSE;
274   }
275   /* We've handled the event, stop processing */
276   return TRUE;
277 }
278 
279 /* Handle motion events by continuing to draw if button 1 is
280  * still held down. The ::motion-notify signal handler receives
281  * a GdkEventMotion struct which contains this information.
282  */
283 static gboolean
motion_notify_event_cb(GtkWidget * widget,GdkEventMotion * event,gpointer data)284 motion_notify_event_cb (GtkWidget      *widget,
285                         GdkEventMotion *event,
286                         gpointer        data)
287 {
288   MarkerSketcherWindow * w = MARKER_SKETCHER_WINDOW(data);
289   /* paranoia check, in case we haven't gotten a configure event */
290   if (w->surface == NULL)
291     return FALSE;
292 
293   if (event->state & GDK_BUTTON1_MASK)
294     draw_brush (widget, event->x, event->y, w);
295   gdk_window_set_cursor(gtk_widget_get_window(widget), w->cursor);
296   /* We've handled it, stop processing */
297   return TRUE;
298 }
299 
300 static void
clean(MarkerSketcherWindow * window)301 clean(MarkerSketcherWindow * window)
302 {
303 
304   if (window->surface)
305     cairo_surface_destroy (window->surface);
306   if (window->history)
307   {
308     clean_surface_list(window->history);
309     g_list_free(window->history);
310   }
311 
312   if (window->future)
313   {
314     clean_surface_list(window->future);
315     g_list_free(window->future);
316   }
317   gtk_widget_hide(GTK_WIDGET(window));
318   gtk_widget_destroy(GTK_WIDGET(window));
319 
320   window->history = NULL;
321   window->future = NULL;
322   window->surface = NULL;
323 }
324 
325 static void
close_cb(GtkButton * widget,gpointer user_data)326 close_cb(GtkButton * widget,
327          gpointer    user_data)
328 {
329   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
330   clean(window);
331 }
332 
333 static void
pen_cb(GtkButton * button,gpointer user_data)334 pen_cb(GtkButton * button,
335        gpointer    user_data)
336 {
337   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
338   window->tool = PEN;
339   window->cursor = gdk_cursor_new_from_name (gtk_widget_get_display(GTK_WIDGET(window)), "crosshair");
340   g_object_unref(window->cursor);
341 }
342 
343 static void
eraser_cb(GtkButton * button,gpointer user_data)344 eraser_cb(GtkButton * button,
345        gpointer    user_data)
346 {
347   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
348   window->tool = ERASER;
349   window->cursor = gdk_cursor_new_from_name (gtk_widget_get_display(GTK_WIDGET(window)), "cell");
350   g_object_unref(window->cursor);
351 }
352 
353 static void
text_cb(GtkButton * button,gpointer user_data)354 text_cb(GtkButton * button,
355        gpointer    user_data)
356 {
357   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
358   window->tool = TEXT;
359   window->cursor = gdk_cursor_new_from_name (gtk_widget_get_display(GTK_WIDGET(window)), "text");
360   g_object_unref(window->cursor);
361 }
362 
363 static void
small_cb(GtkButton * button,gpointer user_data)364 small_cb(GtkButton* button,
365          gpointer   user_data)
366 {
367   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
368   window->size = P_SIZE_S;
369 }
370 
371 static void
medium_cb(GtkButton * button,gpointer user_data)372 medium_cb(GtkButton* button,
373          gpointer   user_data)
374 {
375   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
376   window->size = P_SIZE_M;
377 }
378 
379 static void
large_cb(GtkButton * button,gpointer user_data)380 large_cb(GtkButton* button,
381          gpointer   user_data)
382 {
383   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
384   window->size = P_SIZE_L;
385 }
386 
387 static void
color_set_cb(GtkColorButton * button,gpointer user_data)388 color_set_cb(GtkColorButton*  button,
389              gpointer         user_data)
390 {
391   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
392   gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER(button), &window->color);
393 }
394 
395 char *
create_unic_name(char * base)396 create_unic_name(char * base)
397 {
398   int n = strlen(base)+10;
399   GRand * rand = g_rand_new();
400   int uuid = g_rand_int_range(rand, 0, 9999);
401   char * buffer = malloc(n*sizeof(char));
402   memset(buffer, 0, n);
403   sprintf(buffer, "%s.%d.png", base, uuid);
404   return buffer;
405 }
406 
407 static void
insert_sketch_cb(GtkButton * button,gpointer user_data)408 insert_sketch_cb(GtkButton *  button,
409                  gpointer     user_data)
410 {
411   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(user_data);
412 
413   char * fpath = create_unic_name(window->base_file);
414   cairo_surface_write_to_png(window->surface, fpath);
415   marker_source_view_insert_image(window->source_view, fpath);
416 
417   clean(window);
418 }
419 
420 static void
redo(MarkerSketcherWindow * window)421 redo(MarkerSketcherWindow *window)
422 {
423   GList * last =  g_list_last(window->future);
424   if (last)
425   {
426     window->history = g_list_append(window->history, window->surface);
427     window->surface = last->data;
428     gtk_widget_queue_draw(GTK_WIDGET(window->drawing_area));
429     window->future = g_list_remove_link(window->future, last);
430   }
431 }
432 
433 static void
undo(MarkerSketcherWindow * window)434 undo(MarkerSketcherWindow* window)
435 {
436   GList * last =  g_list_last(window->history);
437   if (last)
438   {
439     window->future = g_list_append(window->future, window->surface);
440     window->surface = last->data;
441 
442 
443     gtk_widget_queue_draw(GTK_WIDGET(window->drawing_area));
444 
445     window->history = g_list_remove_link(window->history, last);
446   }
447 }
448 
449 static gboolean
key_pressed(GtkWidget * widget,GdkEventKey * event,gpointer user_data)450 key_pressed(GtkWidget   *widget,
451             GdkEventKey *event,
452             gpointer     user_data)
453 {
454   gboolean ctrl_pressed = (event->state &  GDK_CONTROL_MASK) != 0;
455   MarkerSketcherWindow * window = MARKER_SKETCHER_WINDOW(widget);
456   if (ctrl_pressed)
457   {
458     switch (event->keyval)
459     {
460     case GDK_KEY_z:
461       undo(window);
462       break;
463     case GDK_KEY_Z:
464       redo(window);
465       break;
466     }
467   }
468   return FALSE;
469 }
470 
471 static void
add_text_cb(GtkWidget * button,gpointer * user_data)472 add_text_cb(GtkWidget *button,
473             gpointer  *user_data)
474 {
475   MarkerSketcherWindow * w = MARKER_SKETCHER_WINDOW(user_data);
476   gtk_popover_popdown(w->text_popover);
477   add_history(w);
478   draw_text(w);
479   gtk_entry_set_text(w->text_entry,"");
480 }
481 
482 static void
close_text_cb(GtkButton * button,gpointer * user_data)483 close_text_cb(GtkButton *button,
484               gpointer  *user_data)
485 {
486   MarkerSketcherWindow * w = MARKER_SKETCHER_WINDOW(user_data);
487   gtk_popover_popdown(w->text_popover);
488   gtk_entry_set_text(w->text_entry,"");
489 }
490 
491 static void
init_ui(MarkerSketcherWindow * window)492 init_ui (MarkerSketcherWindow * window)
493 {
494   GtkBuilder* builder =
495   gtk_builder_new_from_resource(
496     "/com/github/fabiocolacio/marker/ui/marker-sketcher-window.ui");
497 
498   GtkDrawingArea * drawing_area = GTK_DRAWING_AREA(gtk_drawing_area_new());
499   window->drawing_area = drawing_area;
500 
501   gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
502   gtk_window_set_modal(GTK_WINDOW(window), TRUE);
503 
504   GtkBox * vbox = GTK_BOX(gtk_builder_get_object(builder, "vbox"));
505   gtk_box_pack_start(vbox, GTK_WIDGET(drawing_area),TRUE,TRUE, 5);
506 
507   GtkHeaderBar * hbar = GTK_HEADER_BAR(gtk_builder_get_object(builder, "header_bar"));
508 
509   GtkPopover * text_popover = GTK_POPOVER(gtk_builder_get_object(builder, "text_popover"));
510   GtkEntry * text_entry = GTK_ENTRY(gtk_builder_get_object(builder, "text_entry"));
511   window->text_entry = text_entry;
512   window->text_popover = text_popover;
513   gtk_popover_set_relative_to(text_popover, GTK_WIDGET(drawing_area));
514   gtk_popover_set_modal (text_popover, TRUE);
515   gtk_popover_set_position(text_popover, GTK_POS_LEFT);
516 
517   gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
518   gtk_window_set_titlebar(GTK_WINDOW(window), GTK_WIDGET(hbar));
519   gtk_widget_show (GTK_WIDGET (hbar));
520 
521   /* Signals used to handle the backing surface */
522   g_signal_connect (drawing_area, "draw",
523                       G_CALLBACK (draw_cb), window);
524   g_signal_connect (drawing_area,"configure-event",
525                       G_CALLBACK (configure_event_cb), window);
526 
527   /* Event signals */
528   g_signal_connect (drawing_area, "motion-notify-event",
529                       G_CALLBACK (motion_notify_event_cb), window);
530   g_signal_connect (drawing_area, "button-press-event",
531                       G_CALLBACK (button_press_event_cb), window);
532   g_signal_connect (drawing_area, "button-release-event",
533                       G_CALLBACK (button_release_event_cb), window);
534   g_signal_connect (window, "key-press-event",
535                       G_CALLBACK(key_pressed), window);
536   /* Ask to receive events the drawing area doesn't normally
537   * subscribe to. In particular, we need to ask for the
538   * button press and motion notify events that want to handle.
539   */
540   gtk_widget_set_events (GTK_WIDGET(drawing_area), gtk_widget_get_events (GTK_WIDGET(drawing_area))
541                                       | GDK_BUTTON_PRESS_MASK
542                                       | GDK_BUTTON_RELEASE_MASK
543                                       | GDK_POINTER_MOTION_MASK);
544 
545 
546   gtk_widget_show_all(GTK_WIDGET(window));
547 
548   gtk_builder_add_callback_symbol(builder,
549                                   "close_cb",
550                                   G_CALLBACK(close_cb));
551   gtk_builder_add_callback_symbol(builder,
552                                   "pen_cb",
553                                   G_CALLBACK(pen_cb));
554   gtk_builder_add_callback_symbol(builder,
555                                   "eraser_cb",
556                                   G_CALLBACK(eraser_cb));
557   gtk_builder_add_callback_symbol(builder,
558                                   "text_cb",
559                                   G_CALLBACK(text_cb));
560   gtk_builder_add_callback_symbol(builder,
561                                   "color_set_cb",
562                                   G_CALLBACK(color_set_cb));
563   gtk_builder_add_callback_symbol(builder,
564                                   "insert_sketch_cb",
565                                   G_CALLBACK(insert_sketch_cb));
566   gtk_builder_add_callback_symbol(builder,
567                                   "small_cb",
568                                   G_CALLBACK(small_cb));
569   gtk_builder_add_callback_symbol(builder,
570                                   "medium_cb",
571                                   G_CALLBACK(medium_cb));
572   gtk_builder_add_callback_symbol(builder,
573                                   "large_cb",
574                                   G_CALLBACK(large_cb));
575   gtk_builder_add_callback_symbol(builder,
576                                   "close_text_cb",
577                                   G_CALLBACK(close_text_cb));
578   gtk_builder_add_callback_symbol(builder,
579                                   "add_text_cb",
580                                   G_CALLBACK(add_text_cb));
581 
582   gtk_builder_connect_signals(builder, window);
583   g_object_unref(builder);
584 }
585 
586 
587 MarkerSketcherWindow*
marker_sketcher_window_show(GtkWindow * parent,GFile * file,MarkerSourceView * source_view)588 marker_sketcher_window_show(GtkWindow* parent, GFile * file, MarkerSourceView * source_view)
589 {
590   GtkApplication * app = gtk_window_get_application(parent);
591   MarkerSketcherWindow * w= marker_sketcher_window_new(app);
592   gtk_window_set_transient_for(GTK_WINDOW(w), parent);
593   w->source_view = source_view;
594 
595   if (file)
596     w->base_file = g_file_get_path(file);
597   else
598   {
599     char * current_dir = g_get_current_dir ();
600     int n = strlen(current_dir) + 15;
601     w->base_file = malloc(n*sizeof(char));
602     memset(w->base_file, 0, n);
603     sprintf(w->base_file, "%s/%s", current_dir, noname);
604   }
605 
606   gtk_window_present(GTK_WINDOW(w));
607   return w;
608 }
609 
610 MarkerSketcherWindow*
marker_sketcher_window_new(GtkApplication * app)611 marker_sketcher_window_new (GtkApplication * app)
612 {
613     return g_object_new(MARKER_TYPE_SKETCHER_WINDOW, "application", app, NULL);
614 }
615 
616 
617 static void
marker_sketcher_window_class_init(MarkerSketcherWindowClass * class)618 marker_sketcher_window_class_init(MarkerSketcherWindowClass* class)
619 {
620 
621 }
622 
623 static void
marker_sketcher_window_init(MarkerSketcherWindow * sketcher)624 marker_sketcher_window_init (MarkerSketcherWindow *sketcher)
625 {
626   sketcher->surface = NULL;
627   sketcher->history = NULL;
628   sketcher->future = NULL;
629   sketcher->status = FALSE;
630   sketcher->pos_x = 0;
631   sketcher->pos_y = 0;
632   sketcher->tool = PEN;
633   sketcher->size = P_SIZE_M;
634 
635   sketcher->color.alpha = 1;
636   sketcher->color.blue = 0;
637   sketcher->color.green = 0;
638   sketcher->color.red = 0;
639 
640   sketcher->cursor = gdk_cursor_new_from_name (gtk_widget_get_display(GTK_WIDGET(sketcher)), "crosshair");
641   g_object_unref(sketcher->cursor);
642   init_ui(sketcher);
643 }
644