GLUI: add Widget::delete_later(), fixes ParamLabel related crash
[spectmorph.git] / glui / smwindow.cc
1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2
3 #include "pugl/gl.h"
4 #if !__APPLE__
5 #include "GL/glext.h"
6 #endif
7 #include "smwindow.hh"
8 #include "smmenubar.hh"
9 #include "smscrollview.hh"
10 #include "smnativefiledialog.hh"
11 #include "smconfig.hh"
12 #include "smtimer.hh"
13 #include "smshortcut.hh"
14 #include "pugl/cairo_gl.h"
15 #include <string.h>
16 #include <unistd.h>
17 #include <glib.h>
18 #include <sys/time.h>
19
20 using namespace SpectMorph;
21
22 using std::vector;
23 using std::string;
24 using std::min;
25 using std::max;
26
27 struct SpectMorph::CairoGL
28 {
29 private:
30   PuglCairoGL pugl_cairo_gl;
31   cairo_surface_t *surface;
32   int  m_width;
33   int  m_height;
34
35   vector<uint32> tmp_buffer;
36
37 public:
38   cairo_t *cr;
39
40   CairoGL (int width, int height) :
41     m_width (width), m_height (height)
42   {
43     memset (&pugl_cairo_gl, 0, sizeof (pugl_cairo_gl));
44
45     surface = pugl_cairo_gl_create (&pugl_cairo_gl, width, height, 4);
46     cr = cairo_create (surface);
47   }
48   ~CairoGL()
49   {
50     cairo_destroy (cr);
51     cairo_surface_destroy (surface);
52
53     pugl_cairo_gl_free (&pugl_cairo_gl);
54   }
55   void
56   configure()
57   {
58     pugl_cairo_gl_configure (&pugl_cairo_gl, m_width, m_height);
59
60     glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
61                   m_width, m_height, 0,
62                   GL_BGRA, GL_UNSIGNED_BYTE, pugl_cairo_gl.buffer);
63   }
64   void
65   draw (int x, int y, int w, int h)
66   {
67     (void) pugl_cairo_gl_draw; // reimplement this:
68
69     glMatrixMode(GL_MODELVIEW);
70     glLoadIdentity();
71     glViewport(0, 0, m_width, m_height);
72     glClear(GL_COLOR_BUFFER_BIT);
73
74     glPushMatrix();
75     glEnable(GL_TEXTURE_RECTANGLE_ARB);
76     glEnable(GL_TEXTURE_2D);
77
78     void *draw_buffer;
79     if (x == 0 && y == 0 && w == m_width && h == m_height)
80       {
81         // draw full frame
82         draw_buffer = pugl_cairo_gl.buffer;
83       }
84     else
85       {
86         uint32 *src_buffer = reinterpret_cast<uint32 *> (pugl_cairo_gl.buffer);
87         tmp_buffer.resize (w * h);
88
89         for (int by = 0; by < h; by++)
90           {
91             memcpy (&tmp_buffer[by * w], &src_buffer[(by + y) * m_width + x], w * 4);
92           }
93         draw_buffer = tmp_buffer.data();
94       }
95
96     glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0,
97                      x, y, w, h,
98                      GL_BGRA, GL_UNSIGNED_BYTE, draw_buffer);
99
100     glBegin(GL_QUADS);
101     glTexCoord2f(0.0f, (GLfloat)m_height);
102     glVertex2f(-1.0f, -1.0f);
103
104     glTexCoord2f((GLfloat)m_width, (GLfloat)m_height);
105     glVertex2f(1.0f, -1.0f);
106
107     glTexCoord2f((GLfloat)m_width, 0.0f);
108     glVertex2f(1.0f, 1.0f);
109
110     glTexCoord2f(0.0f, 0.0f);
111     glVertex2f(-1.0f, 1.0f);
112     glEnd();
113
114     glDisable(GL_TEXTURE_2D);
115     glDisable(GL_TEXTURE_RECTANGLE_ARB);
116     glPopMatrix();
117   }
118   int
119   width()
120   {
121     return m_width;
122   }
123   int
124   height()
125   {
126     return m_height;
127   }
128 };
129
130 static void
131 on_event (PuglView* view, const PuglEvent* event)
132 {
133   Window *window = reinterpret_cast<Window *> (puglGetHandle (view));
134
135   window->on_event (event);
136 }
137
138 static void
139 on_resize (PuglView *view, int *width, int *height, int *set_hints)
140 {
141   Window *window = reinterpret_cast<Window *> (puglGetHandle (view));
142
143   window->on_resize (width, height);
144 }
145
146 Window::Window (const string& title, int width, int height, PuglNativeWindow win_id, bool resize, PuglNativeWindow transient_parent) :
147   Widget (nullptr, 0, 0, width, height),
148   draw_grid (false)
149 {
150   Config cfg;
151
152   global_scale = cfg.zoom() / 100.0;
153   auto_redraw = false;
154
155   view = puglInit (nullptr, nullptr);
156
157   /* draw 128 bits from random generator to ensure that window class name is unique */
158   string window_class = "SpectMorph_";
159   for (size_t i = 0; i < 4; i++)
160     window_class += string_printf ("%08x", g_random_int());
161
162   int scaled_width, scaled_height;
163   get_scaled_size (&scaled_width, &scaled_height);
164
165   puglInitWindowClass (view, window_class.c_str());
166   puglInitWindowSize (view, scaled_width, scaled_height);
167   //puglInitWindowMinSize (view, 256, 256);
168   puglInitResizable (view, resize);
169   puglIgnoreKeyRepeat (view, true);
170   if (win_id)
171     puglInitWindowParent (view, win_id);
172   if (transient_parent)
173     puglInitTransientFor (view, transient_parent);
174   puglCreateWindow (view, title.c_str());
175
176   puglSetHandle (view, this);
177   puglSetEventFunc (view, ::on_event);
178   puglSetResizeFunc (view, ::on_resize);
179
180   cairo_gl.reset (new CairoGL (scaled_width, scaled_height));
181   update_full();
182
183   puglEnterContext (view);
184   glEnable (GL_DEPTH_TEST);
185   glDepthFunc (GL_LESS);
186   glClearColor (0.4f, 0.4f, 0.4f, 1.0f);
187   cairo_gl->configure();
188   // printf ("OpenGL Version: %s\n",(const char*) glGetString(GL_VERSION));
189   puglLeaveContext(view, false);
190
191   set_background_color (ThemeColor::WINDOW_BG);
192 }
193
194 Window::~Window()
195 {
196   puglDestroy (view);
197
198   /* this code needs to work if remove_timer & add_timer are called from one of the destructors */
199   for (size_t i = 0; i < timers.size(); i++)
200     {
201       if (timers[i])
202         delete timers[i];
203     }
204   for (size_t i = 0; i < timers.size(); i++)
205     assert (timers[i] == nullptr);
206
207   /* cleanup shortcuts: this code needs to work if remove_shortcut & add_shortcut are called from one of the destructors */
208   for (size_t i = 0; i < shortcuts.size(); i++)
209     {
210       if (shortcuts[i])
211         delete shortcuts[i];
212     }
213   for (size_t i = 0; i < shortcuts.size(); i++)
214     assert (shortcuts[i] == nullptr);
215
216   /* cleanup child windows */
217   for (size_t i = 0; i < child_windows.size(); i++)
218     {
219       if (child_windows[i])
220         delete child_windows[i];
221     }
222   /* do not use auto here */
223   for (size_t i = 0; i < delete_later_widgets.size(); i++)
224     delete delete_later_widgets[i];
225 }
226
227 void
228 Window::on_widget_deleted (Widget *child)
229 {
230   /* cheap weak pointer emulation */
231   if (mouse_widget == child)
232     mouse_widget = nullptr;
233   if (enter_widget == child)
234     enter_widget = nullptr;
235   if (menu_widget == child)
236     menu_widget = nullptr;
237   if (keyboard_focus_widget == child)
238     keyboard_focus_widget = nullptr;
239   for (auto& w : delete_later_widgets)
240     {
241       if (w == child)
242         w = nullptr;
243     }
244   if (dialog_widget == child)
245     {
246       update_full();
247       dialog_widget = nullptr;
248     }
249 }
250
251 static vector<Widget *>
252 crawl_widgets (const vector<Widget *>& widgets)
253 {
254   vector<Widget *> all_widgets;
255
256   for (auto w : widgets)
257     {
258       all_widgets.push_back (w);
259       auto c_result = crawl_widgets (w->children);
260       all_widgets.insert (all_widgets.end(), c_result.begin(), c_result.end());
261     }
262   return all_widgets;
263 }
264
265 vector<Widget *>
266 Window::crawl_widgets()
267 {
268   return ::crawl_widgets ({ this });
269 }
270
271 static int
272 get_layer (Widget *w, Widget *menu_widget, Widget *dialog_widget)
273 {
274   if (w == menu_widget)
275     return 1;
276   if (w == dialog_widget)
277     return 2;
278
279   if (w->parent)
280     return get_layer (w->parent, menu_widget, dialog_widget);
281   else
282     return 0; // no parent
283 }
284
285 static bool
286 get_visible_recursive (Widget *w)
287 {
288   if (!w->visible())
289     return false;
290
291   if (w->parent)
292     return get_visible_recursive (w->parent);
293   else
294     return true; // no parent
295 }
296
297 namespace {
298
299 struct IRect
300 {
301   int x, y, w, h;
302
303   IRect (const Rect& r, double scale)
304   {
305     x = r.x() * scale;
306     y = r.y() * scale;
307     w = r.width() * scale;
308     h = r.height() * scale;
309   }
310
311   void
312   grow (int px)
313   {
314     x -= px;
315     y -= px;
316     w += px * 2;
317     h += px * 2;
318   }
319
320   void
321   clip (int width, int height)
322   {
323     x = sm_bound (0, x, width);
324     y = sm_bound (0, y, height);
325     w = sm_bound (0, w, width - x);
326     h = sm_bound (0, h, height - y);
327   }
328 };
329
330 };
331
332 void
333 Window::on_display()
334 {
335   // glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
336
337   cairo_save (cairo_gl->cr);
338
339   /* for debugging, we want a rectangle around the area we would normally update */
340   const bool draw_update_region_rect = debug_update_region && !update_full_redraw;
341   if (draw_update_region_rect)
342     update_full_redraw = true;   // always draw full frames in debug mode
343
344   Rect update_region_larger;
345   if (!update_full_redraw)
346     {
347       // setup clipping - only need to redraw part of the screen
348       IRect r (update_region, global_scale);
349
350       // since we have to convert double coordinates to integer, we add a bit of extra space
351       r.grow (4);
352
353       cairo_rectangle (cairo_gl->cr, r.x, r.y, r.w, r.h);
354       cairo_clip (cairo_gl->cr);
355
356       update_region_larger = Rect (r.x / global_scale, r.y / global_scale, r.w / global_scale, r.h / global_scale);
357     }
358
359   for (int layer = 0; layer < 3; layer++)
360     {
361       if (dialog_widget && layer == 2) /* draw rest of ui darker if dialog is open */
362         {
363           cairo_rectangle (cairo_gl->cr, 0, 0, width * global_scale, height * global_scale);
364           cairo_set_source_rgba (cairo_gl->cr, 0.0, 0, 0, 0.5);
365           cairo_fill (cairo_gl->cr);
366         }
367       for (auto w : crawl_widgets())
368         {
369           if (get_layer (w, menu_widget, dialog_widget) == layer && get_visible_recursive (w))
370             {
371               Rect visible_rect = w->abs_visible_rect();
372               if (!update_full_redraw)
373                 {
374                   // only redraw changed parts
375                   visible_rect = visible_rect.intersection (update_region_larger);
376                 }
377               if (!visible_rect.empty())
378                 {
379                   cairo_t *cr = cairo_gl->cr;
380
381                   cairo_save (cr);
382                   cairo_scale (cr, global_scale, global_scale);
383
384                   DrawEvent devent;
385
386                   // local coordinates
387                   cairo_translate (cr, w->abs_x(), w->abs_y());
388                   if (w->clipping())
389                     {
390                       // translate to widget local coordinates
391                       visible_rect.move_to (visible_rect.x() - w->abs_x(), visible_rect.y() - w->abs_y());
392
393                       cairo_rectangle (cr, visible_rect.x(), visible_rect.y(), visible_rect.width(), visible_rect.height());
394                       cairo_clip (cr);
395
396                       devent.rect = visible_rect;
397                     }
398
399                   if (draw_grid && w == enter_widget)
400                     w->debug_fill (cr);
401
402                   devent.cr = cr;
403                   w->draw (devent);
404                   cairo_restore (cr);
405                 }
406             }
407         }
408     }
409
410   if (have_file_dialog || have_popup_window)
411     {
412       cairo_rectangle (cairo_gl->cr, 0, 0, width * global_scale, height * global_scale);
413       cairo_set_source_rgba (cairo_gl->cr, 0.0, 0, 0, 0.5);
414       cairo_fill (cairo_gl->cr);
415     }
416
417   if (draw_grid)
418     {
419       cairo_t *cr = cairo_gl->cr;
420
421       cairo_save (cr);
422       cairo_scale (cr, global_scale, global_scale);
423
424       cairo_set_source_rgb (cr, 0, 0, 0);
425       cairo_set_line_width (cr, 0.5);
426
427       for (double x = 8; x < width; x += 8)
428         {
429           for (double y = 8; y < height; y += 8)
430             {
431               cairo_move_to (cr, x - 2, y);
432               cairo_line_to (cr, x + 2, y);
433               cairo_move_to (cr, x, y - 2);
434               cairo_line_to (cr, x, y + 2);
435             }
436         }
437       cairo_stroke (cr);
438       cairo_restore (cr);
439     }
440   cairo_restore (cairo_gl->cr);
441
442   if (draw_update_region_rect)
443     {
444       cairo_t *cr = cairo_gl->cr;
445
446       cairo_save (cr);
447       cairo_scale (cr, global_scale, global_scale);
448       cairo_rectangle (cr, update_region.x(), update_region.y(), update_region.width(), update_region.height());
449       cairo_set_source_rgb (cr, 1.0, 0.4, 0.4);
450       cairo_set_line_width (cr, 3.0);
451       cairo_stroke (cr);
452       cairo_restore (cr);
453     }
454
455   if (update_full_redraw)
456     {
457       cairo_gl->draw (0, 0, cairo_gl->width(), cairo_gl->height());
458     }
459   else
460     {
461       IRect draw_region (update_region, global_scale);
462
463       /* the number of pixels we blit is somewhat smaller than "update_region_larger", so we have
464        *
465        * update_region < draw_region < update_region larger
466        *
467        * the extra space should compensate for the effect of fractional
468        * coordinates (which do not render at exact pixel boundaries)
469        */
470       draw_region.grow (2);
471
472       draw_region.clip (cairo_gl->width(), cairo_gl->height());
473
474       cairo_gl->draw (draw_region.x, draw_region.y, draw_region.w, draw_region.h);
475     }
476   // clear update region (will be assigned by update[_full] before next redraw)
477   update_region = Rect();
478   update_full_redraw = false;
479 }
480
481 template<class T>
482 void
483 cleanup_null (vector<T *>& vec)
484 {
485   vector<T *> new_vec;
486   for (auto object : vec)
487     if (object)
488       new_vec.push_back (object);
489
490   if (vec.size() > new_vec.size())
491     vec = new_vec;
492 }
493
494 void
495 Window::process_events()
496 {
497   if (popup_window)
498     {
499       popup_window->process_events();
500
501       if (!have_popup_window)
502         popup_window.reset(); // must be deleted after (not during) process_events */
503     }
504   if (native_file_dialog)
505     {
506       native_file_dialog->process_events();
507
508       if (!have_file_dialog)
509         {
510           /* file dialog closed - must be deleted after (not during) process_events */
511           native_file_dialog.reset();
512         }
513     }
514
515   /* do not use auto here, since timers may get modified */
516   for (size_t i = 0; i < timers.size(); i++)
517     {
518       Timer *timer = timers[i];
519       if (timer)
520         timer->process_events();
521     }
522   /* timers array may have been modified - remove null entries */
523   cleanup_null (timers);
524
525   for (size_t i = 0; i < child_windows.size(); i++)
526     {
527       Window *window = child_windows[i];
528       if (window)
529         {
530           window->process_events();
531           if (child_windows[i] == nullptr)
532             {
533               /* window closed - must be deleted after (not during) process_events */
534               delete window;
535             }
536         }
537     }
538   cleanup_null (child_windows);
539
540   /* do not use auto here */
541   for (size_t i = 0; i < delete_later_widgets.size(); i++)
542     {
543       delete delete_later_widgets[i];
544
545       assert (!delete_later_widgets[i]);
546     }
547   cleanup_null (delete_later_widgets);
548
549   if (0)
550     {
551       timeval tv;
552       gettimeofday (&tv, 0);
553
554       static double last_time = -1;
555       const double time = tv.tv_sec + tv.tv_usec / 1000000.0;
556       const double delta_time = time - last_time;
557
558       if (last_time > 0)
559         sm_debug ("process_delta_time %f %f\n", /* time diff */ delta_time, /* frames per second */ 1.0 / delta_time);
560
561       last_time = time;
562     }
563   puglProcessEvents (view);
564 }
565
566 static void
567 dump_event (const PuglEvent *event)
568 {
569   switch (event->type)
570     {
571       case PUGL_NOTHING:            printf ("Event: nothing\n");
572         break;
573       case PUGL_BUTTON_PRESS:       printf ("Event: button press\n");
574         break;
575       case PUGL_BUTTON_RELEASE:     printf ("Event: button release\n");
576         break;
577       case PUGL_CONFIGURE:          printf ("Event: configure w%f h%f\n", event->configure.width, event->configure.height);
578         break;
579       case PUGL_EXPOSE:             printf ("Event: expose x%f y%f w%f h%f\n", event->expose.x, event->expose.y, event->expose.width, event->expose.height);
580         break;
581       case PUGL_CLOSE:              printf ("Event: close\n");
582         break;
583       case PUGL_KEY_PRESS:          printf ("Event: key press %c\n", event->key.character);
584         break;
585       case PUGL_KEY_RELEASE:        printf ("Event: key release\n");
586         break;
587       case PUGL_ENTER_NOTIFY:       printf ("Event: enter\n");
588         break;
589       case PUGL_LEAVE_NOTIFY:       printf ("Event: leave\n");
590         break;
591       case PUGL_MOTION_NOTIFY:      printf ("Event: motion\n");
592         break;
593       case PUGL_SCROLL:             printf ("Event: scroll: dx=%f dy=%f\n", event->scroll.dx, event->scroll.dy);
594         break;
595       case PUGL_FOCUS_IN:           printf ("Event: focus in\n");
596         break;
597       case PUGL_FOCUS_OUT:          printf ("Event: focus out\n");
598         break;
599     }
600 }
601
602 void
603 Window::on_event (const PuglEvent* event)
604 {
605   if (0)
606     dump_event (event);
607
608   double ex, ey; /* global scale translated */
609   Widget *current_widget = nullptr;
610
611   /* as long as the file dialog or popup window is open, ignore user input */
612   const bool ignore_input = have_file_dialog || have_popup_window;
613   if (ignore_input && event->type != PUGL_EXPOSE && event->type != PUGL_CONFIGURE)
614     return;
615
616   switch (event->type)
617     {
618       bool key_handled;
619
620       case PUGL_BUTTON_PRESS:
621         ex = event->button.x / global_scale;
622         ey = event->button.y / global_scale;
623
624         mouse_widget = find_widget_xy (ex, ey);
625         if (keyboard_focus_widget && keyboard_focus_release_on_click)
626           {
627             keyboard_focus_widget->focus_out_event();
628             keyboard_focus_widget = nullptr;
629           }
630         else
631           {
632             mouse_widget->mouse_press (ex - mouse_widget->abs_x(), ey - mouse_widget->abs_y());
633           }
634
635         if (auto_redraw)
636           update_full();
637         break;
638       case PUGL_BUTTON_RELEASE:
639         ex = event->button.x / global_scale;
640         ey = event->button.y / global_scale;
641         if (mouse_widget)
642           {
643             Widget *w = mouse_widget;
644             w->mouse_release (ex - w->abs_x(), ey - w->abs_y());
645             mouse_widget = nullptr;
646           }
647         if (auto_redraw)
648           update_full();
649         break;
650       case PUGL_MOTION_NOTIFY:
651         ex = event->motion.x / global_scale;
652         ey = event->motion.y / global_scale;
653         if (mouse_widget) /* user interaction with one widget */
654           {
655             current_widget = mouse_widget;
656           }
657         else
658           {
659             current_widget = find_widget_xy (ex, ey);
660             if (enter_widget != current_widget)
661               {
662                 if (enter_widget)
663                   enter_widget->leave_event();
664
665                 enter_widget = current_widget;
666                 current_widget->enter_event();
667               }
668           }
669         current_widget->motion (ex - current_widget->abs_x(), ey - current_widget->abs_y());
670         if (auto_redraw)
671           update_full();
672         break;
673       case PUGL_SCROLL:
674         ex = event->scroll.x / global_scale;
675         ey = event->scroll.y / global_scale;
676         if (mouse_widget)
677           current_widget = mouse_widget;
678         else
679           current_widget = find_widget_xy (ex, ey);
680
681         while (current_widget)
682           {
683             if (current_widget->scroll (event->scroll.dx, event->scroll.dy))
684               break;
685
686             current_widget = current_widget->parent;
687           }
688         if (auto_redraw)
689           update_full();
690         break;
691       case PUGL_KEY_PRESS:
692         key_handled = false;
693         /* do not use auto here, since shortcuts may get modified */
694         cleanup_null (shortcuts);
695         for (size_t i = 0; i < shortcuts.size(); i++)
696           {
697             Shortcut *shortcut = shortcuts[i];
698             if (!key_handled && shortcut)
699               key_handled = shortcut->key_press_event (event->key);
700           }
701         if (!key_handled && keyboard_focus_widget)
702           keyboard_focus_widget->key_press_event (event->key);
703         else if (!key_handled)
704           {
705             if (event->key.character == 'g')
706               draw_grid = !draw_grid;
707             else if (event->key.character == 'u')
708               debug_update_region = !debug_update_region;
709           }
710         if (auto_redraw)
711           update_full();
712         break;
713       case PUGL_CLOSE:
714         if (m_close_callback)
715           m_close_callback();
716         break;
717       case PUGL_EXPOSE:
718         on_display();
719         break;
720       case PUGL_CONFIGURE:
721         {
722           int w, h;
723           get_scaled_size (&w, &h);
724           cairo_gl.reset (new CairoGL (w, h));
725
726           // on windows, the coordinates of the event often doesn't match actual size
727           // cairo_gl.reset (new CairoGL (event->configure.width, event->configure.height));
728         }
729         puglEnterContext (view);
730         cairo_gl->configure();
731         puglLeaveContext (view, false);
732         update_full();
733         break;
734       default:
735         break;
736     }
737 }
738
739 Widget *
740 Window::find_widget_xy (double ex, double ey)
741 {
742   Widget *widget = this;
743
744   if (menu_widget)
745     widget = menu_widget;  // active menu => only children of the menu get clicks
746
747   if (dialog_widget)
748     widget = dialog_widget;
749
750   for (auto w : ::crawl_widgets ({ widget })) // which child gets the click?
751     {
752       if (get_visible_recursive (w) && w->recursive_enabled() && w->abs_visible_rect().contains (ex, ey))
753         {
754           widget = w;
755         }
756     }
757   return widget;
758 }
759
760 void
761 Window::show()
762 {
763   puglPostRedisplay (view);
764   puglShowWindow (view);
765 }
766
767 void
768 Window::open_file_dialog (const string& title, const string& filter_title, const string& filter, std::function<void(string)> callback)
769 {
770   PuglNativeWindow win_id = puglGetNativeWindow (view);
771
772   file_dialog_callback = callback;
773   have_file_dialog = true;
774
775   native_file_dialog.reset (NativeFileDialog::create (win_id, true, title, filter_title, filter));
776   connect (native_file_dialog->signal_file_selected, this, &Window::on_file_selected);
777 }
778
779 void
780 Window::save_file_dialog (const string& title, const string& filter_title, const string& filter, std::function<void(string)> callback)
781 {
782   PuglNativeWindow win_id = puglGetNativeWindow (view);
783
784   file_dialog_callback = callback;
785   have_file_dialog = true;
786
787   native_file_dialog.reset (NativeFileDialog::create (win_id, false, title, filter_title, filter));
788   connect (native_file_dialog->signal_file_selected, this, &Window::on_file_selected);
789 }
790
791 void
792 Window::on_file_selected (const std::string& filename)
793 {
794   if (file_dialog_callback)
795     {
796       file_dialog_callback (filename);
797       file_dialog_callback = nullptr;
798     }
799   have_file_dialog = false;
800   update_full();
801 }
802
803 void
804 Window::need_update (Widget *widget, const Rect *changed_rect)
805 {
806   if (widget)
807     {
808       Rect widget_rect = widget->abs_visible_rect();
809       if (changed_rect)
810         {
811           /* if changed rect is set, we only need to redraw a part of the widget */
812           Rect abs_changed_rect;
813           abs_changed_rect = Rect (changed_rect->x() + widget->abs_x(),
814                                    changed_rect->y() + widget->abs_y(),
815                                    changed_rect->width(),
816                                    changed_rect->height());
817           widget_rect = widget_rect.intersection (abs_changed_rect);
818         }
819       update_region = update_region.rect_union (widget_rect);
820     }
821   else
822     update_full_redraw = true;
823
824   puglPostRedisplay (view);
825 }
826
827 Window *
828 Window::window()
829 {
830   return this;
831 }
832
833 void
834 Window::wait_event_fps()
835 {
836   /* tradeoff between UI responsiveness and cpu usage caused by thread wakeups
837    *
838    * 60 fps should make the UI look smooth
839    */
840   const double frames_per_second = 60;
841
842   usleep (1000 * 1000 / frames_per_second);
843 }
844
845 void
846 Window::wait_for_event()
847 {
848   bool active_timer = false;
849   for (auto t : timers)
850     {
851       if (t)
852         active_timer = t->active() || active_timer;
853     }
854   if (native_file_dialog || popup_window || active_timer)
855     {
856       /* need to wait for events of this view, and handle io for file dialog */
857       wait_event_fps();
858     }
859   else
860     {
861       puglWaitForEvent (view);
862     }
863 }
864
865 void
866 Window::set_menu_widget (Widget *widget)
867 {
868   menu_widget = widget;
869   mouse_widget = widget;
870 }
871
872 void
873 Window::set_close_callback (const std::function<void()>& callback)
874 {
875   m_close_callback = callback;
876 }
877
878 void
879 Window::set_keyboard_focus (Widget *widget, bool release_on_click)
880 {
881   keyboard_focus_widget = widget;
882   keyboard_focus_widget->focus_event();
883   keyboard_focus_release_on_click = release_on_click;
884 }
885
886 void
887 Window::set_dialog_widget (Widget *widget)
888 {
889   dialog_widget = widget;
890 }
891
892 void
893 Window::set_popup_window (Window *pwin)
894 {
895   if (pwin)
896     {
897       // take ownership
898       popup_window.reset (pwin);
899
900       have_popup_window = true;
901     }
902   else
903     {
904       have_popup_window = false;
905     }
906   update_full();
907 }
908
909 void
910 Window::add_child_window (Window *cwin)
911 {
912   child_windows.push_back (cwin);
913 }
914
915 void
916 Window::remove_child_window (Window *cwin)
917 {
918   for (auto& c : child_windows)
919     {
920       if (c == cwin)
921         c = nullptr;
922     }
923 }
924
925 PuglNativeWindow
926 Window::native_window()
927 {
928   return puglGetNativeWindow (view);
929 }
930
931 void
932 Window::fill_zoom_menu (Menu *menu)
933 {
934   menu->clear();
935
936   for (int z = 70; z <= 500; )
937     {
938       int w = width * z / 100;
939       int h = height * z / 100;
940
941       string text = string_locale_printf ("%d%%   -   %dx%d", z, w, h);
942
943       if (sm_round_positive (window()->gui_scaling() * 100) == z)
944         text += "   -   current zoom";
945       MenuItem *item = menu->add_item (text);
946       connect (item->signal_clicked, [=]() {
947         window()->set_gui_scaling (z / 100.);
948
949         // we need to refill the menu to update the "current zoom" entry
950         fill_zoom_menu (menu);
951       });
952
953       if (z >= 400)
954         z += 50;
955       else if (z >= 300)
956         z += 25;
957       else if (z >= 200)
958         z += 20;
959       else
960         z += 10;
961     }
962 }
963
964 void
965 Window::set_gui_scaling (double s)
966 {
967   global_scale = s;
968
969   /* restart with this gui scaling next time */
970   Config cfg;
971
972   cfg.set_zoom (sm_round_positive (s * 100));
973   cfg.store();
974
975   /* (1) typically, at this point we notify the host that our window will have a new size */
976   signal_update_size();
977
978   /* (2) and we ensure that our window size will be changed via pugl */
979   puglPostResize (view);
980 }
981
982 double
983 Window::gui_scaling()
984 {
985   return global_scale;
986 }
987
988 void
989 Window::on_resize (int *win_width, int *win_height)
990 {
991   get_scaled_size (win_width, win_height);
992 }
993
994 void
995 Window::get_scaled_size (int *w, int *h)
996 {
997   *w = width * global_scale;
998   *h = height * global_scale;
999 }
1000
1001 void
1002 Window::add_timer (Timer *timer)
1003 {
1004   timers.push_back (timer);
1005 }
1006
1007 void
1008 Window::remove_timer (Timer *timer)
1009 {
1010   for (auto& t : timers)
1011     {
1012       if (t == timer)
1013         t = nullptr;
1014     }
1015 }
1016
1017 void
1018 Window::add_shortcut (Shortcut *shortcut)
1019 {
1020   shortcuts.push_back (shortcut);
1021 }
1022
1023 void
1024 Window::remove_shortcut (Shortcut *shortcut)
1025 {
1026   for (auto& s : shortcuts)
1027     {
1028       if (s == shortcut)
1029         s = nullptr;
1030     }
1031 }
1032
1033 void
1034 Window::add_delete_later (Widget *widget)
1035 {
1036   delete_later_widgets.push_back (widget);
1037 }