1 #include <cstring>
2 #include <cstdint>
3 #include <typeinfo>
4 #include <cairo.h>
5 #include <gtk/gtk.h>
6 #include <toys/toy-framework-2.h>
7 
8 #include <cairo-features.h>
9 #if CAIRO_HAS_PDF_SURFACE
10 #include <cairo-pdf.h>
11 #endif
12 #if CAIRO_HAS_SVG_SURFACE
13 #include <cairo-svg.h>
14 #endif
15 
16 GtkApplicationWindow* the_window = nullptr;
17 static GtkWidget *the_canvas = nullptr;
18 Toy* the_toy = nullptr;
19 int the_requested_height = 0;
20 int the_requested_width = 0;
21 gchar **the_emulated_argv = nullptr;
22 
23 gchar *arg_spool_filename = nullptr;
24 gchar *arg_handles_filename = nullptr;
25 gchar *arg_screenshot_filename = nullptr;
26 gchar **arg_extra_files = nullptr;
27 
28 //Utility functions
29 
uniform()30 double uniform() {
31     return double(rand()) / RAND_MAX;
32 }
33 
from_hsv(float H,float S,float V,float A)34 colour colour::from_hsv( float H,          // hue shift (in degrees)
35                          float S,          // saturation shift (scalar)
36                          float V,          // value multiplier (scalar)
37                          float A
38     )
39 {
40     double inr = 1;
41     double ing = 0;
42     double inb = 0;
43     float k = V/3;
44     float a = V*S*cos(H)/3;
45     float b = V*S*sin(H)/3;
46 
47     return colour(
48         (k+2*a)*inr -     2*b*ing +    (k-a-b)*inb,
49         (-k+a+3*b)*inr + (3*a-b)*ing + (-k+a+2*b)*inb,
50         (2*k-2*a)*inr +     2*b*ing +  (2*k+a+b)*inb,
51         A);
52 }
53 
54  // Given H,S,L in range of 0-1
55 
56  // Returns a Color (RGB struct) in range of 0-255
57 
from_hsl(float h,float sl,float l,float a)58 colour colour::from_hsl(float h, float sl, float l, float a) {
59     h /= M_PI*2;
60     colour rgba(l,l,l,a); // default to gray
61 
62     double v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl);
63 
64     if (v > 0) {
65         double m;
66         double sv;
67         int sextant;
68         double fract, vsf, mid1, mid2;
69 
70         m = l + l - v;
71         sv = (v - m ) / v;
72         h *= 6.0;
73         sextant = (int)h;
74         fract = h - sextant;
75         vsf = v * sv * fract;
76         mid1 = m + vsf;
77         mid2 = v - vsf;
78         switch (sextant%6) {
79             case 0:
80                 rgba.r = v;
81                 rgba.g = mid1;
82                 rgba.b = m;
83                 break;
84 
85             case 1:
86                 rgba.r = mid2;
87                 rgba.g = v;
88                 rgba.b = m;
89                 break;
90 
91             case 2:
92                 rgba.r = m;
93                 rgba.g = v;
94                 rgba.b = mid1;
95                 break;
96 
97             case 3:
98                 rgba.r = m;
99                 rgba.g = mid2;
100                 rgba.b = v;
101                 break;
102 
103             case 4:
104                 rgba.r = mid1;
105                 rgba.g = m;
106                 rgba.b = v;
107                 break;
108 
109             case 5:
110                 rgba.r = v;
111                 rgba.g = m;
112                 rgba.b = mid2;
113                 break;
114         }
115     }
116     return rgba;
117 }
118 
cairo_set_source_rgba(cairo_t * cr,colour c)119 void cairo_set_source_rgba(cairo_t* cr, colour c) {
120     cairo_set_source_rgba(cr, c.r, c.g, c.b, c.a);
121 }
122 
draw_text(cairo_t * cr,Geom::Point loc,const char * txt,bool bottom,const char * fontdesc)123 void draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom, const char* fontdesc) {
124     PangoLayout* layout = pango_cairo_create_layout (cr);
125     pango_layout_set_text(layout, txt, -1);
126     PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc);
127     pango_layout_set_font_description(layout, font_desc);
128     pango_font_description_free (font_desc);
129     PangoRectangle logical_extent;
130     pango_layout_get_pixel_extents(layout, NULL, &logical_extent);
131     cairo_move_to(cr, loc - Geom::Point(0, bottom ? logical_extent.height : 0));
132     pango_cairo_show_layout(cr, layout);
133 }
134 
draw_text(cairo_t * cr,Geom::Point loc,const std::string & txt,bool bottom,const std::string & fontdesc)135 void draw_text(cairo_t *cr, Geom::Point loc, const std::string& txt, bool bottom, const std::string& fontdesc) {
136     draw_text(cr, loc, txt.c_str(), bottom, fontdesc.c_str());
137 }
138 
draw_number(cairo_t * cr,Geom::Point pos,int num,std::string name,bool bottom)139 void draw_number(cairo_t *cr, Geom::Point pos, int num, std::string name, bool bottom) {
140     std::ostringstream number;
141     if (name.size())
142 	number << name;
143     number << num;
144     draw_text(cr, pos, number.str().c_str(), bottom);
145 }
146 
draw_number(cairo_t * cr,Geom::Point pos,unsigned num,std::string name,bool bottom)147 void draw_number(cairo_t *cr, Geom::Point pos, unsigned num, std::string name, bool bottom) {
148     std::ostringstream number;
149     if (name.size())
150 	number << name;
151     number << num;
152     draw_text(cr, pos, number.str().c_str(), bottom);
153 }
154 
draw_number(cairo_t * cr,Geom::Point pos,double num,std::string name,bool bottom)155 void draw_number(cairo_t *cr, Geom::Point pos, double num, std::string name, bool bottom) {
156     std::ostringstream number;
157     if (name.size())
158 	number << name;
159     number << num;
160     draw_text(cr, pos, number.str().c_str(), bottom);
161 }
162 
163 //Framework Accessors
redraw()164 void redraw() { gtk_widget_queue_draw(GTK_WIDGET(the_window)); }
165 
draw(cairo_t * cr,std::ostringstream * notify,int width,int height,bool,std::ostringstream * timer_stream)166 void Toy::draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool /*save*/, std::ostringstream *timer_stream)
167 {
168     if(should_draw_bounds() == 1) {
169         cairo_set_source_rgba (cr, 0., 0., 0, 0.8);
170         cairo_set_line_width (cr, 0.5);
171         for(unsigned i = 1; i < 4; i+=2) {
172             cairo_move_to(cr, 0, i*width/4);
173             cairo_line_to(cr, width, i*width/4);
174             cairo_move_to(cr, i*width/4, 0);
175             cairo_line_to(cr, i*width/4, height);
176         }
177     }
178     else if(should_draw_bounds() == 2) {
179         cairo_set_source_rgba (cr, 0., 0., 0, 0.8);
180         cairo_set_line_width (cr, 0.5);
181 	cairo_move_to(cr, 0, width/2);
182 	cairo_line_to(cr, width, width/2);
183 	cairo_move_to(cr, width/2, 0);
184 	cairo_line_to(cr, width/2, height);
185     }
186 
187     cairo_set_line_width (cr, 1);
188     for(auto & handle : handles) {
189         cairo_set_source_rgb (cr, handle->rgb[0], handle->rgb[1], handle->rgb[2]);
190 	handle->draw(cr, should_draw_numbers());
191     }
192 
193     cairo_set_source_rgba (cr, 0.5, 0, 0, 1);
194     if(selected && mouse_down == true)
195 	selected->draw(cr, should_draw_numbers());
196 
197     cairo_set_source_rgba (cr, 0.5, 0.25, 0, 1);
198     cairo_stroke(cr);
199 
200     cairo_set_source_rgba (cr, 0., 0.5, 0, 0.8);
201     {
202         *notify << std::ends;
203         draw_text(cr, Geom::Point(0, height-notify_offset), notify->str().c_str(), true);
204     }
205     if(show_timings) {
206         *timer_stream << std::ends;
207         draw_text(cr, Geom::Point(0, notify_offset), timer_stream->str().c_str(), false);
208     }
209 }
210 
mouse_moved(GdkEventMotion * e)211 void Toy::mouse_moved(GdkEventMotion* e)
212 {
213     Geom::Point mouse(e->x, e->y);
214 
215     if(e->state & (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) {
216         if(selected)
217 	    selected->move_to(hit_data, old_mouse_point, mouse);
218     }
219     old_mouse_point = mouse;
220     redraw();
221 }
222 
mouse_pressed(GdkEventButton * e)223 void Toy::mouse_pressed(GdkEventButton* e) {
224     Geom::Point mouse(e->x, e->y);
225     selected = NULL;
226     hit_data = NULL;
227     canvas_click_button = e->button;
228     if(e->button == 1) {
229         for(auto & handle : handles) {
230     	    void * hit = handle->hit(mouse);
231     	    if(hit) {
232     		selected = handle;
233     		hit_data = hit;
234     	    }
235         }
236         mouse_down = true;
237     }
238     old_mouse_point = mouse;
239     redraw();
240 }
241 
scroll(GdkEventScroll *)242 void Toy::scroll(GdkEventScroll* /*e*/) {
243 }
244 
canvas_click(Geom::Point at,int button)245 void Toy::canvas_click(Geom::Point at, int button) {
246     (void)at;
247     (void)button;
248 }
249 
mouse_released(GdkEventButton * e)250 void Toy::mouse_released(GdkEventButton* e) {
251     if(selected == NULL) {
252         Geom::Point mouse(e->x, e->y);
253         canvas_click(mouse, canvas_click_button);
254         canvas_click_button = 0;
255     }
256     selected = NULL;
257     hit_data = NULL;
258     if(e->button == 1)
259 	mouse_down = false;
260     redraw();
261 }
262 
load(FILE * f)263 void Toy::load(FILE* f) {
264     char data[1024];
265     if (fscanf(f, "%1024s", data)) {
266         name = data;
267     }
268     for(auto & handle : handles) {
269         handle->load(f);
270     }
271 }
272 
save(FILE * f)273 void Toy::save(FILE* f) {
274 	fprintf(f, "%s\n", name.c_str());
275     for(auto & handle : handles)
276 	handle->save(f);
277 }
278 
279 //Gui Event Callbacks
280 
show_about_dialog(GSimpleAction *,GVariant *,gpointer)281 void show_about_dialog(GSimpleAction *, GVariant *, gpointer) {
282     GtkWidget* about_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
283     gtk_window_set_title(GTK_WINDOW(about_window), "About");
284     gtk_window_set_resizable(GTK_WINDOW(about_window), FALSE);
285 
286     GtkWidget* about_text = gtk_text_view_new();
287     GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(about_text));
288     gtk_text_buffer_set_text(buf, "Toy lib2geom application", -1);
289     gtk_container_add(GTK_CONTAINER(about_window), about_text);
290 
291     gtk_widget_show_all(about_window);
292 }
293 
quit(GSimpleAction *,GVariant *,gpointer)294 void quit(GSimpleAction *, GVariant *, gpointer) {
295     g_application_quit(g_application_get_default());
296 }
297 
read_point(FILE * f)298 Geom::Point read_point(FILE* f) {
299     Geom::Point p;
300     for(unsigned i = 0; i < 2; i++)
301         assert(fscanf(f, " %lf ", &p[i]));
302     return p;
303 }
304 
read_interval(FILE * f)305 Geom::Interval read_interval(FILE* f) {
306     Geom::Interval p;
307     Geom::Coord a, b;
308     assert(fscanf(f, " %lf ", &a));
309     assert(fscanf(f, " %lf ", &b));
310     p.setEnds(a, b);
311     return p;
312 }
313 
open_handles(GSimpleAction *,GVariant *,gpointer)314 void open_handles(GSimpleAction *, GVariant *, gpointer) {
315     if (!the_toy) return;
316 	GtkWidget* d = gtk_file_chooser_dialog_new(
317 	     "Open handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_OPEN,
318 	     "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL);
319     if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
320         const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
321         FILE* f = fopen(filename, "r");
322         the_toy->load(f);
323         fclose(f);
324     }
325     gtk_widget_destroy(d);
326 }
327 
save_handles(GSimpleAction *,GVariant *,gpointer)328 void save_handles(GSimpleAction *, GVariant *, gpointer) {
329     if (!the_toy) return;
330     GtkWidget* d = gtk_file_chooser_dialog_new(
331          "Save handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE,
332          "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL);
333     if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
334         const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
335         FILE* f = fopen(filename, "w");
336         the_toy->save(f);
337         fclose(f);
338     }
339     gtk_widget_destroy(d);
340 }
341 
write_image(const char * filename)342 void write_image(const char* filename) {
343     cairo_surface_t* cr_s;
344     unsigned l = strlen(filename);
345     int width = gdk_window_get_width(gtk_widget_get_window(the_canvas));
346     int height = gdk_window_get_height(gtk_widget_get_window(the_canvas));
347     bool save_png = false;
348 
349     if (l >= 4 && strcmp(filename + l - 4, ".png") == 0) {
350         cr_s = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32, width, height );
351         save_png = true;
352     }
353 
354 #if CAIRO_HAS_PDF_SURFACE
355     else if (l >= 4 && strcmp(filename + l - 4, ".pdf") == 0)
356         cr_s = cairo_pdf_surface_create(filename, width, height);
357 #endif
358 #if CAIRO_HAS_SVG_SURFACE
359 #if CAIRO_HAS_PDF_SURFACE
360     else
361 #endif
362         cr_s = cairo_svg_surface_create(filename, width, height);
363 #endif
364     cairo_t* cr = cairo_create(cr_s);
365 
366     if(save_png) {
367         cairo_save(cr);
368         cairo_set_source_rgb(cr, 1,1,1);
369         cairo_paint(cr);
370         cairo_restore(cr);
371     }
372     if(the_toy != NULL) {
373         std::ostringstream * notify = new std::ostringstream;
374         std::ostringstream * timer_stream = new std::ostringstream;
375         the_toy->draw(cr, notify, width, height, true, timer_stream);
376         delete notify;
377         delete timer_stream;
378     }
379 
380     cairo_show_page(cr);
381     if(save_png)
382         cairo_surface_write_to_png(cr_s, filename);
383     cairo_destroy (cr);
384     cairo_surface_destroy (cr_s);
385 }
386 
save_cairo(GSimpleAction *,GVariant *,gpointer)387 void save_cairo(GSimpleAction *, GVariant *, gpointer) {
388     GtkWidget* d = gtk_file_chooser_dialog_new(
389         "Save file as svg, pdf or png", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE,
390         "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL);
391     if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
392         const gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
393         write_image(filename);
394     }
395     gtk_widget_destroy(d);
396 }
397 
delete_event(GtkWidget *,GdkEventAny *,gpointer)398 static gint delete_event(GtkWidget*, GdkEventAny*, gpointer) {
399     quit(nullptr, nullptr, nullptr);
400     return FALSE;
401 }
402 
403 
toggle_action(GSimpleAction * action,GVariant *,gpointer)404 static void toggle_action(GSimpleAction *action, GVariant *, gpointer) {
405     GVariant *state = g_action_get_state(G_ACTION(action));
406     g_action_change_state(G_ACTION(action), g_variant_new_boolean(!g_variant_get_boolean(state)));
407     g_variant_unref(state);
408 }
409 
set_show_timings(GSimpleAction * action,GVariant * variant,gpointer)410 static void set_show_timings(GSimpleAction *action, GVariant *variant, gpointer) {
411     the_toy->show_timings = g_variant_get_boolean(variant);
412     g_simple_action_set_state(action, variant);
413 }
414 
draw_callback(GtkWidget * widget,cairo_t * cr)415 static gboolean draw_callback(GtkWidget *widget, cairo_t *cr)
416 {
417     int width = gdk_window_get_width(gtk_widget_get_window(widget));
418     int height = gdk_window_get_height(gtk_widget_get_window(widget));
419 
420     std::ostringstream notify;
421 
422     static bool resized = false;
423     if(!resized) {
424 	Geom::Rect alloc_size(Geom::Interval(0, width),
425 			      Geom::Interval(0, height));
426 	if(the_toy != NULL)
427 	    the_toy->resize_canvas(alloc_size);
428 	resized = true;
429     }
430     cairo_rectangle(cr, 0, 0, width, height);
431     cairo_set_source_rgba(cr,1,1,1,1);
432     cairo_fill(cr);
433     if (the_toy != NULL) {
434         std::ostringstream * timer_stream = new std::ostringstream;
435 
436         if (the_toy->spool_file) {
437             the_toy->save(the_toy->spool_file);
438         }
439 
440         the_toy->draw(cr, &notify, width, height, false, timer_stream);
441         delete timer_stream;
442     }
443 
444     return TRUE;
445 }
446 
mouse_motion_event(GtkWidget * widget,GdkEventMotion * e,gpointer data)447 static gint mouse_motion_event(GtkWidget* widget, GdkEventMotion* e, gpointer data) {
448     (void)(data);
449     (void)(widget);
450 
451     if(the_toy != NULL) the_toy->mouse_moved(e);
452 
453     return FALSE;
454 }
455 
mouse_event(GtkWidget * widget,GdkEventButton * e,gpointer data)456 static gint mouse_event(GtkWidget* widget, GdkEventButton* e, gpointer data) {
457     (void)(data);
458     (void)(widget);
459 
460     if(the_toy != NULL) the_toy->mouse_pressed(e);
461 
462     return FALSE;
463 }
464 
scroll_event(GtkWidget * widget,GdkEventScroll * e,gpointer data)465 static gint scroll_event(GtkWidget* widget, GdkEventScroll* e, gpointer data) {
466     (void)(data);
467     (void)(widget);
468     if(the_toy != NULL) the_toy->scroll(e);
469 
470     return FALSE;
471 }
472 
mouse_release_event(GtkWidget * widget,GdkEventButton * e,gpointer data)473 static gint mouse_release_event(GtkWidget* widget, GdkEventButton* e, gpointer data) {
474     (void)(data);
475     (void)(widget);
476 
477     if(the_toy != NULL) the_toy->mouse_released(e);
478 
479     return FALSE;
480 }
481 
key_press_event(GtkWidget * widget,GdkEventKey * e,gpointer data)482 static gint key_press_event(GtkWidget *widget, GdkEventKey *e, gpointer data) {
483     (void)(data);
484     (void)(widget);
485 
486     if(the_toy != NULL) the_toy->key_hit(e);
487 
488     return FALSE;
489 }
490 
size_allocate_event(GtkWidget * widget,GtkAllocation * allocation,gpointer data)491 static gint size_allocate_event(GtkWidget* widget, GtkAllocation *allocation, gpointer data) {
492     (void)(data);
493     (void)(widget);
494 
495     Geom::Rect alloc_size(Geom::Interval(allocation->x, allocation->x+ allocation->width),
496 			  Geom::Interval(allocation->y, allocation->y+allocation->height));
497     if(the_toy != NULL) the_toy->resize_canvas(alloc_size);
498 
499     return FALSE;
500 }
501 
502 
503 const char *the_builder_xml = R"xml(
504 <?xml version="1.0" encoding="UTF-8"?>
505 <interface>
506   <menu id="menu">
507     <submenu>
508       <attribute name="label">File</attribute>
509       <section>
510         <item>
511           <attribute name="label">Open Handles...</attribute>
512           <attribute name="action">app.open-handles</attribute>
513         </item>
514         <item>
515           <attribute name="label">Save Handles...</attribute>
516           <attribute name="action">app.save-handles</attribute>
517         </item>
518       </section>
519       <section>
520         <item>
521           <attribute name="label">Save as SVG of PDF...</attribute>
522           <attribute name="action">app.save-image</attribute>
523         </item>
524       </section>
525       <section>
526         <item>
527           <attribute name="label">Show Timings</attribute>
528           <attribute name="action">app.show-timings</attribute>
529         </item>
530         <item>
531           <attribute name="label">Quit</attribute>
532           <attribute name="action">app.quit</attribute>
533         </item>
534       </section>
535     </submenu>
536     <submenu>
537       <attribute name="label">Help</attribute>
538       <item>
539         <attribute name="label">About...</attribute>
540         <attribute name="action">app.about</attribute>
541       </item>
542     </submenu>
543   </menu>
544 </interface>
545 )xml";
546 
547 static GActionEntry the_actions[] =
548 {
549     {"open-handles", open_handles, nullptr, nullptr, nullptr},
550     {"save-handles", save_handles, nullptr, nullptr, nullptr},
551     {"save-image", save_cairo, nullptr, nullptr, nullptr},
552     {"show-timings", toggle_action, nullptr, "false", set_show_timings},
553     {"quit", quit, nullptr, nullptr, nullptr},
554     {"about", show_about_dialog, nullptr, nullptr, nullptr},
555 };
556 
557 static GOptionEntry const the_options[] = {
558     {"handles", 'h', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_handles_filename,
559      "Load handle positions from given file", "FILE"},
560     {"spool", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_spool_filename,
561      "Record all interaction to the given file", "FILE"},
562     {"screenshot", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_screenshot_filename,
563      "Take screenshot and exit", nullptr},
564     {G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &arg_extra_files,
565      "Additional data files", "FILES..."},
566     {nullptr, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr},
567 };
568 
569 static void activate(GApplication *app, gpointer);
570 static void startup(GApplication *app, gpointer);
571 
init(int argc,char ** argv,Toy * t,int width,int height)572 void init(int argc, char **argv, Toy* t, int width, int height) {
573     the_toy = t;
574     the_requested_width = width;
575     the_requested_height = height;
576 
577     std::string app_name = "org.inkscape.lib2geom.toy.";
578     char const *dir_pos = strrchr(argv[0], G_DIR_SEPARATOR);
579     std::string argv_name = dir_pos ? dir_pos + 1 : argv[0];
580 
581     // Erase extension for Windows
582     size_t dot_pos = argv_name.rfind('.');
583     if (dot_pos != std::string::npos) {
584         argv_name.erase(dot_pos);
585     }
586     the_toy->name = argv_name;
587     app_name += argv_name;
588 
589     GtkApplication* app = gtk_application_new(app_name.c_str(), G_APPLICATION_FLAGS_NONE);
590     g_application_add_main_option_entries(G_APPLICATION(app), the_options);
591     g_action_map_add_action_entries(G_ACTION_MAP(app), the_actions, G_N_ELEMENTS(the_actions), nullptr);
592     g_signal_connect(G_OBJECT(app), "startup", G_CALLBACK(startup), nullptr);
593     g_signal_connect(G_OBJECT(app), "activate", G_CALLBACK(activate), nullptr);
594 
595     g_application_run(G_APPLICATION(app), argc, argv);
596     g_object_unref(app);
597 }
598 
startup(GApplication * app,gpointer)599 static void startup(GApplication *app, gpointer) {
600     GtkBuilder *builder = gtk_builder_new_from_string(the_builder_xml, -1);
601     GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
602     gtk_application_set_menubar(GTK_APPLICATION(app), menu);
603     g_object_unref(builder);
604 }
605 
activate(GApplication * app,gpointer)606 static void activate(GApplication *app, gpointer) {
607     if (arg_spool_filename) {
608         the_toy->spool_file = fopen(arg_spool_filename, "w");
609     }
610 
611     int const emulated_argc = arg_extra_files ? g_strv_length(arg_extra_files) + 1 : 1;
612     gchar const **emulated_argv = new gchar const*[emulated_argc];
613     emulated_argv[0] = the_toy->name.c_str();
614     for (int i = 1; i < emulated_argc; ++i) {
615         emulated_argv[i] = arg_extra_files[i-1];
616     }
617     the_toy->first_time(emulated_argc, const_cast<char**>(emulated_argv));
618     delete[] emulated_argv;
619 
620     if (arg_handles_filename) {
621         FILE *handles_file = fopen(arg_handles_filename, "r");
622         the_toy->load(handles_file);
623         fclose(handles_file);
624     }
625 
626     if (arg_screenshot_filename) {
627         write_image(arg_screenshot_filename);
628         g_application_quit(app);
629         return;
630     }
631 
632     the_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(g_application_get_default())));
633     gtk_window_set_title(GTK_WINDOW(the_window), the_toy->name.c_str());
634     g_signal_connect(G_OBJECT(the_window), "delete_event", G_CALLBACK(delete_event), NULL);
635 
636     the_canvas = gtk_drawing_area_new();
637     gtk_widget_add_events(the_canvas, (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK));
638     g_signal_connect(G_OBJECT(the_canvas), "draw", G_CALLBACK(draw_callback), 0);
639     g_signal_connect(G_OBJECT(the_canvas), "scroll-event", G_CALLBACK(scroll_event), 0);
640     g_signal_connect(G_OBJECT(the_canvas), "button-press-event", G_CALLBACK(mouse_event), 0);
641     g_signal_connect(G_OBJECT(the_canvas), "button-release-event", G_CALLBACK(mouse_release_event), 0);
642     g_signal_connect(G_OBJECT(the_canvas), "motion-notify-event", G_CALLBACK(mouse_motion_event), 0);
643     g_signal_connect(G_OBJECT(the_canvas), "key-press-event", G_CALLBACK(key_press_event), 0);
644     g_signal_connect(G_OBJECT(the_canvas), "size-allocate", G_CALLBACK(size_allocate_event), 0);
645 
646     gtk_container_add(GTK_CONTAINER(the_window), the_canvas);
647     gtk_window_set_default_size(GTK_WINDOW(the_window), the_requested_width, the_requested_height);
648     gtk_widget_show_all(GTK_WIDGET(the_window));
649 
650     // Make sure the canvas can receive key press events.
651     gtk_widget_set_can_focus(the_canvas, TRUE);
652     gtk_widget_grab_focus(the_canvas);
653     assert(gtk_widget_is_focus(the_canvas));
654 }
655 
656 
draw(cairo_t * cr,bool)657 void Toggle::draw(cairo_t *cr, bool /*annotes*/) {
658     cairo_pattern_t* source = cairo_get_source(cr);
659     double rc, gc, bc, aa;
660     cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa);
661     cairo_set_source_rgba(cr,0,0,0,1);
662     cairo_rectangle(cr, bounds.left(), bounds.top(),
663 		    bounds.width(), bounds.height());
664     if(on) {
665 	cairo_fill(cr);
666 	cairo_set_source_rgba(cr,1,1,1,1);
667     } //else cairo_stroke(cr);
668     cairo_stroke(cr);
669     draw_text(cr, bounds.corner(0) + Geom::Point(5,2), text);
670     cairo_set_source_rgba(cr, rc, gc, bc, aa);
671 }
672 
toggle()673 void Toggle::toggle() {
674     on = !on;
675 }
set(bool state)676 void Toggle::set(bool state) {
677     on = state;
678 }
679 
680 
handle_click(GdkEventButton * e)681 void Toggle::handle_click(GdkEventButton* e) {
682     if(bounds.contains(Geom::Point(e->x, e->y)) && e->button == 1) toggle();
683 }
684 
hit(Geom::Point mouse)685 void* Toggle::hit(Geom::Point mouse)
686 {
687     if (bounds.contains(mouse))
688     {
689         toggle();
690         return this;
691     }
692     return 0;
693 }
694 
toggle_events(std::vector<Toggle> & ts,GdkEventButton * e)695 void toggle_events(std::vector<Toggle> &ts, GdkEventButton* e) {
696     for(auto & t : ts) t.handle_click(e);
697 }
698 
draw_toggles(cairo_t * cr,std::vector<Toggle> & ts)699 void draw_toggles(cairo_t *cr, std::vector<Toggle> &ts) {
700     for(auto & t : ts) t.draw(cr);
701 }
702 
703 
704 
value() const705 Slider::value_type Slider::value() const
706 {
707     Slider::value_type v = m_handle.pos[m_dir] - m_pos[m_dir];
708     v =  ((m_max - m_min) / m_length) * v;
709     //std::cerr << "v : " << v << std::endl;
710     if (m_step != 0)
711     {
712         int k = std::floor(v / m_step);
713         v = k * m_step;
714     }
715     v = v + m_min;
716     //std::cerr << "v : " << v << std::endl;
717     return v;
718 }
719 
value(Slider::value_type _value)720 void Slider::value(Slider::value_type _value)
721 {
722     if ( _value < m_min ) _value = m_min;
723     if ( _value > m_max ) _value = m_max;
724     if (m_step != 0)
725     {
726         _value = _value - m_min;
727         int k = std::floor(_value / m_step);
728         _value = k * m_step + m_min;
729     }
730     m_handle.pos[m_dir]
731            = (m_length / (m_max - m_min)) * (_value - m_min) + m_pos[m_dir];
732 }
733 
max_value(Slider::value_type _value)734 void Slider::max_value(Slider::value_type _value)
735 {
736     Slider::value_type v = value();
737     m_max = _value;
738     value(v);
739 }
740 
min_value(Slider::value_type _value)741 void Slider::min_value(Slider::value_type _value)
742 {
743     Slider::value_type v = value();
744     m_min = _value;
745     value(v);
746 }
747 
748 // dir = X horizontal slider dir = Y vertical slider
geometry(Geom::Point _pos,Slider::value_type _length,Geom::Dim2 _dir)749 void Slider::geometry( Geom::Point _pos,
750                        Slider::value_type _length,
751                        Geom::Dim2 _dir )
752 {
753     Slider::value_type v = value();
754     m_pos = _pos;
755     m_length = _length;
756     m_dir = _dir;
757     Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 );
758     m_handle.pos[fix_dir] = m_pos[fix_dir];
759     value(v);
760 }
761 
draw(cairo_t * cr,bool annotate)762 void Slider::draw(cairo_t* cr, bool annotate)
763 {
764     cairo_pattern_t* source = cairo_get_source(cr);
765     double rc, gc, bc, aa;
766     cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa);
767     double lw = cairo_get_line_width(cr);
768     std::ostringstream os;
769     os << m_label << ": " << (*m_formatter)(value());
770     cairo_set_source_rgba(cr, 0.1, 0.1, 0.7, 1.0);
771     cairo_set_line_width(cr, 0.7);
772     m_handle.draw(cr, annotate);
773     cairo_stroke(cr);
774     cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0);
775     cairo_set_line_width(cr, 0.4);
776     m_handle.draw(cr, annotate);
777     cairo_move_to(cr, m_pos[Geom::X], m_pos[Geom::Y]);
778     Geom::Point offset;
779     if ( m_dir == Geom::X )
780     {
781         cairo_rel_line_to(cr, m_length, 0);
782         offset = Geom::Point(0,5);
783     }
784     else
785     {
786         cairo_rel_line_to(cr, 0, m_length);
787         offset = Geom::Point(5,0);
788     }
789     cairo_stroke(cr);
790     cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
791     draw_text(cr, m_pos + offset, os.str().c_str());
792     cairo_set_source_rgba(cr, rc, gc, bc, aa);
793     cairo_set_line_width(cr, lw);
794 }
795 
move_to(void * hit,Geom::Point om,Geom::Point m)796 void Slider::move_to(void* hit, Geom::Point om, Geom::Point m)
797 {
798     // fix_dir == ! m_dir
799     Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 );
800     m[fix_dir] = m_pos[fix_dir];
801     double diff = m[m_dir] - m_pos[m_dir];
802 //        if (m_step != 0)
803 //        {
804 //            double step =  (m_step * m_length) / (m_max - m_min) ;
805 //            int k = std::floor(diff / step);
806 //            double v = k * step;
807 //            m[m_dir] = v + m_pos[m_dir];
808 //        }
809     if ( diff < 0 ) m[m_dir] = m_pos[m_dir];
810     if ( diff > m_length ) m[m_dir] = m_pos[m_dir] + m_length;
811     m_handle.move_to(hit, om, m);
812 }
813 
814 
815 
draw(cairo_t * cr,bool)816 void PointHandle::draw(cairo_t *cr, bool /*annotes*/) {
817     draw_circ(cr, pos);
818 }
819 
hit(Geom::Point mouse)820 void* PointHandle::hit(Geom::Point mouse) {
821     if(Geom::distance(mouse, pos) < 5)
822 	return this;
823     return 0;
824 }
825 
move_to(void *,Geom::Point,Geom::Point m)826 void PointHandle::move_to(void* /*hit*/, Geom::Point /*om*/, Geom::Point m) {
827     pos = m;
828 }
829 
load(FILE * f)830 void PointHandle::load(FILE* f) {
831     pos = read_point(f);
832 }
833 
save(FILE * f)834 void PointHandle::save(FILE* f) {
835     fprintf(f, "%lf %lf\n", pos[0], pos[1]);
836 }
837 
draw(cairo_t * cr,bool annotes)838 void PointSetHandle::draw(cairo_t *cr, bool annotes) {
839     for(unsigned i = 0; i < pts.size(); i++) {
840 	draw_circ(cr, pts[i]);
841         if(annotes) draw_number(cr, pts[i], i, name);
842     }
843 }
844 
hit(Geom::Point mouse)845 void* PointSetHandle::hit(Geom::Point mouse) {
846     for(auto & pt : pts) {
847 	if(Geom::distance(mouse, pt) < 5)
848 	    return (void*)(&pt);
849     }
850     return 0;
851 }
852 
move_to(void * hit,Geom::Point,Geom::Point m)853 void PointSetHandle::move_to(void* hit, Geom::Point /*om*/, Geom::Point m) {
854     if(hit) {
855 	*(Geom::Point*)hit = m;
856     }
857 }
858 
load(FILE * f)859 void PointSetHandle::load(FILE* f) {
860     int n = 0;
861     assert(1 == fscanf(f, "%d\n", &n));
862     pts.clear();
863     for(int i = 0; i < n; i++) {
864 	pts.push_back(read_point(f));
865     }
866 }
867 
save(FILE * f)868 void PointSetHandle::save(FILE* f) {
869     fprintf(f, "%d\n", (int)pts.size());
870     for(auto & pt : pts) {
871 	fprintf(f, "%lf %lf\n", pt[0], pt[1]);
872     }
873 }
874 
875 #include <2geom/bezier-to-sbasis.h>
876 
asBezier()877 Geom::D2<Geom::SBasis> PointSetHandle::asBezier() {
878     return handles_to_sbasis(pts.begin(), size()-1);
879 }
880 
draw(cairo_t * cr,bool)881 void RectHandle::draw(cairo_t *cr, bool /*annotes*/) {
882     cairo_rectangle(cr, pos);
883     cairo_stroke(cr);
884     if(show_center_handle) {
885         draw_circ(cr, pos.midpoint());
886     }
887     draw_text(cr, pos.corner(0), name);
888 }
889 
hit(Geom::Point mouse)890 void* RectHandle::hit(Geom::Point mouse) {
891     if(show_center_handle) {
892 	if(Geom::distance(mouse, pos.midpoint()) < 5)
893             return (void*)(intptr_t)1;
894     }
895     for(int i = 0; i < 4; i++) {
896 	if(Geom::distance(mouse, pos.corner(i)) < 5)
897             return (void*)(intptr_t)(2+i);
898     }
899     for(int i = 0; i < 4; i++) {
900         Geom::LineSegment ls(pos.corner(i), pos.corner(i+1));
901 	if(Geom::distance(ls.pointAt(ls.nearestTime(mouse)),mouse) < 5)
902             return (void*)(intptr_t)(6+i);
903     }
904     return 0;
905 
906 }
907 
move_to(void * hit,Geom::Point om,Geom::Point m)908 void RectHandle::move_to(void* hit, Geom::Point om, Geom::Point m) {
909     using Geom::X;
910     using Geom::Y;
911 
912     unsigned h = (unsigned)(uintptr_t)(hit);
913     if(h == 1)
914         pos += (m-om);
915     else if(h >= 2 and h <= 5) {// corners
916         int xi = (h-2)& 1;
917         int yi = (h-2)&2;
918         if(yi)
919             xi = 1-xi; // clockwise
920         if (xi) {
921             pos[X].setMax(m[0]);
922         } else {
923             pos[X].setMin(m[0]);
924         }
925         if (yi/2) {
926             pos[Y].setMax(m[1]);
927         } else {
928             pos[Y].setMax(m[1]);
929         }
930     } else if(h >= 6 and h <= 9) {// edges
931         int side, d;
932         switch(h-6) {
933             case 0: d = 1; side = 0; break;
934             case 1: d = 0; side = 1; break;
935             case 2: d = 1; side = 1; break;
936             case 3: d = 0; side = 0; break;
937         }
938         if (side) {
939             pos[d].setMax(m[d]);
940         } else {
941             pos[d].setMin(m[d]);
942         }
943     }
944 }
945 
load(FILE * f)946 void RectHandle::load(FILE* f) {
947     assert(0 == fscanf(f, "r\n"));
948     for(int i = 0; i < 2; i++) {
949 	pos[i] = read_interval(f);
950     }
951 
952 }
953 
save(FILE * f)954 void RectHandle::save(FILE* f) {
955     fprintf(f, "r\n");
956     for(unsigned i = 0; i < 2; i++) {
957 	fprintf(f, "%lf %lf\n", pos[i].min(), pos[i].max());
958     }
959 }
960 
961 
962 
963 /*
964 	Local Variables:
965 	mode:c++
966 	c-file-style:"stroustrup"
967 	c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
968 	indent-tabs-mode:nil
969 	fill-column:99
970 	End:
971       */
972 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
973