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