2b70ee9988237214a9fce31df6bf34fdca6b29d1
[blender.git] / source / blender / windowmanager / intern / wm_window.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2007 Blender Foundation but based
19  * on ghostwinlay.c (C) 2001-2002 by NaN Holding BV
20  * All rights reserved.
21  *
22  * Contributor(s): Blender Foundation, 2008
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  */
26
27 /** \file blender/windowmanager/intern/wm_window.c
28  *  \ingroup wm
29  *
30  * Window management, wrap GHOST.
31  */
32
33 #include <math.h>
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37
38 #include "DNA_listBase.h"
39 #include "DNA_screen_types.h"
40 #include "DNA_windowmanager_types.h"
41
42 #include "MEM_guardedalloc.h"
43
44 #include "GHOST_C-api.h"
45
46 #include "BLI_math.h"
47 #include "BLI_blenlib.h"
48 #include "BLI_utildefines.h"
49
50 #include "BLT_translation.h"
51
52 #include "BKE_blender.h"
53 #include "BKE_context.h"
54 #include "BKE_library.h"
55 #include "BKE_global.h"
56 #include "BKE_main.h"
57
58
59 #include "RNA_access.h"
60
61 #include "WM_api.h"
62 #include "WM_types.h"
63 #include "wm.h"
64 #include "wm_draw.h"
65 #include "wm_window.h"
66 #include "wm_subwindow.h"
67 #include "wm_event_system.h"
68
69 #include "ED_screen.h"
70 #include "ED_fileselect.h"
71
72 #include "UI_interface.h"
73 #include "UI_resources.h"
74
75 #include "PIL_time.h"
76
77 #include "GPU_draw.h"
78 #include "GPU_extensions.h"
79 #include "GPU_init_exit.h"
80 #include "GPU_glew.h"
81 #include "BLF_api.h"
82
83 /* for assert */
84 #ifndef NDEBUG
85 #  include "BLI_threads.h"
86 #endif
87
88 /* the global to talk to ghost */
89 static GHOST_SystemHandle g_system = NULL;
90
91 typedef enum WinOverrideFlag {
92         WIN_OVERRIDE_GEOM     = (1 << 0),
93         WIN_OVERRIDE_WINSTATE = (1 << 1)
94 } WinOverrideFlag;
95
96 /* set by commandline */
97 static struct WMInitStruct {
98         /* window geometry */
99         int size_x, size_y;
100         int start_x, start_y;
101
102         int windowstate;
103         WinOverrideFlag override_flag;
104
105         bool window_focus;
106         bool native_pixels;
107 } wm_init_state = {0, 0, 0, 0, GHOST_kWindowStateNormal, 0, true, true};
108
109 /* ******** win open & close ************ */
110
111 /* XXX this one should correctly check for apple top header...
112  * done for Cocoa : returns window contents (and not frame) max size*/
113 void wm_get_screensize(int *r_width, int *r_height)
114 {
115         unsigned int uiwidth;
116         unsigned int uiheight;
117
118         GHOST_GetMainDisplayDimensions(g_system, &uiwidth, &uiheight);
119         *r_width = uiwidth;
120         *r_height = uiheight;
121 }
122
123 /* size of all screens (desktop), useful since the mouse is bound by this */
124 void wm_get_desktopsize(int *r_width, int *r_height)
125 {
126         unsigned int uiwidth;
127         unsigned int uiheight;
128
129         GHOST_GetAllDisplayDimensions(g_system, &uiwidth, &uiheight);
130         *r_width = uiwidth;
131         *r_height = uiheight;
132 }
133
134 /* keeps offset and size within monitor bounds */
135 /* XXX solve dual screen... */
136 static void wm_window_check_position(rcti *rect)
137 {
138         int width, height, d;
139
140         wm_get_screensize(&width, &height);
141
142         if (rect->xmin < 0) {
143                 rect->xmax -= rect->xmin;
144                 rect->xmin  = 0;
145         }
146         if (rect->ymin < 0) {
147                 rect->ymax -= rect->ymin;
148                 rect->ymin  = 0;
149         }
150         if (rect->xmax > width) {
151                 d = rect->xmax - width;
152                 rect->xmax -= d;
153                 rect->xmin -= d;
154         }
155         if (rect->ymax > height) {
156                 d = rect->ymax - height;
157                 rect->ymax -= d;
158                 rect->ymin -= d;
159         }
160
161         if (rect->xmin < 0) rect->xmin = 0;
162         if (rect->ymin < 0) rect->ymin = 0;
163 }
164
165
166 static void wm_ghostwindow_destroy(wmWindow *win)
167 {
168         if (win->ghostwin) {
169                 GHOST_DisposeWindow(g_system, win->ghostwin);
170                 win->ghostwin = NULL;
171                 win->multisamples = 0;
172         }
173 }
174
175 /* including window itself, C can be NULL.
176  * ED_screen_exit should have been called */
177 void wm_window_free(bContext *C, wmWindowManager *wm, wmWindow *win)
178 {
179         wmTimer *wt, *wtnext;
180
181         /* update context */
182         if (C) {
183                 WM_event_remove_handlers(C, &win->handlers);
184                 WM_event_remove_handlers(C, &win->modalhandlers);
185
186                 if (CTX_wm_window(C) == win)
187                         CTX_wm_window_set(C, NULL);
188         }
189
190         /* always set drawable and active to NULL,
191          * prevents non-drawable state of main windows (bugs #22967 and #25071, possibly #22477 too) */
192         wm->windrawable = NULL;
193         wm->winactive = NULL;
194
195         /* end running jobs, a job end also removes its timer */
196         for (wt = wm->timers.first; wt; wt = wtnext) {
197                 wtnext = wt->next;
198                 if (wt->win == win && wt->event_type == TIMERJOBS)
199                         wm_jobs_timer_ended(wm, wt);
200         }
201
202         /* timer removing, need to call this api function */
203         for (wt = wm->timers.first; wt; wt = wtnext) {
204                 wtnext = wt->next;
205                 if (wt->win == win)
206                         WM_event_remove_timer(wm, win, wt);
207         }
208
209         if (win->eventstate) MEM_freeN(win->eventstate);
210
211         wm_event_free_all(win);
212         wm_subwindows_free(win);
213
214         wm_draw_data_free(win);
215
216         wm_ghostwindow_destroy(win);
217
218         MEM_freeN(win->stereo3d_format);
219
220         MEM_freeN(win);
221 }
222
223 static int find_free_winid(wmWindowManager *wm)
224 {
225         wmWindow *win;
226         int id = 1;
227
228         for (win = wm->windows.first; win; win = win->next)
229                 if (id <= win->winid)
230                         id = win->winid + 1;
231
232         return id;
233 }
234
235 /* don't change context itself */
236 wmWindow *wm_window_new(bContext *C)
237 {
238         wmWindowManager *wm = CTX_wm_manager(C);
239         wmWindow *win = MEM_callocN(sizeof(wmWindow), "window");
240
241         BLI_addtail(&wm->windows, win);
242         win->winid = find_free_winid(wm);
243
244         win->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Stereo 3D Format (window)");
245
246         return win;
247 }
248
249
250 /* part of wm_window.c api */
251 wmWindow *wm_window_copy(bContext *C, wmWindow *win_src)
252 {
253         Main *bmain = CTX_data_main(C);
254         wmWindow *win_dst = wm_window_new(C);
255
256         win_dst->posx = win_src->posx + 10;
257         win_dst->posy = win_src->posy;
258         win_dst->sizex = win_src->sizex;
259         win_dst->sizey = win_src->sizey;
260
261         /* duplicate assigns to window */
262         win_dst->screen = ED_screen_duplicate(bmain, win_dst, win_src->screen);
263         BLI_strncpy(win_dst->screenname, win_dst->screen->id.name + 2, sizeof(win_dst->screenname));
264         win_dst->screen->winid = win_dst->winid;
265
266         win_dst->screen->do_refresh = true;
267         win_dst->screen->do_draw = true;
268
269         win_dst->drawmethod = U.wmdrawmethod;
270
271         BLI_listbase_clear(&win_dst->drawdata);
272
273         *win_dst->stereo3d_format = *win_src->stereo3d_format;
274
275         return win_dst;
276 }
277
278 /**
279  * A higher level version of copy that tests the new window can be added.
280  * (called from the operator directly)
281  */
282 wmWindow *wm_window_copy_test(bContext *C, wmWindow *win_src)
283 {
284         wmWindowManager *wm = CTX_wm_manager(C);
285         wmWindow *win_dst;
286
287         win_dst = wm_window_copy(C, win_src);
288
289         WM_check(C);
290
291         if (win_dst->ghostwin) {
292                 WM_event_add_notifier(C, NC_WINDOW | NA_ADDED, NULL);
293                 return win_dst;
294         }
295         else {
296                 wm_window_close(C, wm, win_dst);
297                 return NULL;
298         }
299 }
300
301
302 /* -------------------------------------------------------------------- */
303 /** \name Quit Confirmation Dialog
304  * \{ */
305
306 /** Cancel quitting and close the dialog */
307 static void wm_block_confirm_quit_cancel(bContext *C, void *arg_block, void *UNUSED(arg))
308 {
309         wmWindow *win = CTX_wm_window(C);
310         UI_popup_block_close(C, win, arg_block);
311 }
312
313 /** Discard the file changes and quit */
314 static void wm_block_confirm_quit_discard(bContext *C, void *arg_block, void *UNUSED(arg))
315 {
316         wmWindow *win = CTX_wm_window(C);
317         UI_popup_block_close(C, win, arg_block);
318         WM_exit(C);
319 }
320
321 /* Save changes and quit */
322 static void wm_block_confirm_quit_save(bContext *C, void *arg_block, void *UNUSED(arg))
323 {
324         PointerRNA props_ptr;
325         wmWindow *win = CTX_wm_window(C);
326
327         UI_popup_block_close(C, win, arg_block);
328
329         wmOperatorType *ot = WM_operatortype_find("WM_OT_save_mainfile", false);
330
331         WM_operator_properties_create_ptr(&props_ptr, ot);
332         RNA_boolean_set(&props_ptr, "exit", true);
333         /* No need for second confirmation popup. */
334         RNA_boolean_set(&props_ptr, "check_existing", false);
335         WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr);
336         WM_operator_properties_free(&props_ptr);
337 }
338
339
340 /* Build the confirm dialog UI */
341 static uiBlock *block_create_confirm_quit(struct bContext *C, struct ARegion *ar, void *UNUSED(arg1))
342 {
343         Main *bmain = CTX_data_main(C);
344
345         uiStyle *style = UI_style_get();
346         uiBlock *block = UI_block_begin(C, ar, "confirm_quit_popup", UI_EMBOSS);
347
348         UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
349         UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
350         UI_block_emboss_set(block, UI_EMBOSS);
351
352         uiLayout *layout = UI_block_layout(
353                 block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 10, 2, U.widget_unit * 24, U.widget_unit * 6, 0, style);
354
355         /* Text and some vertical space */
356         {
357                 char *message;
358                 if (BKE_main_blendfile_path(bmain)[0] == '\0') {
359                         message = BLI_strdup(IFACE_("This file has not been saved yet. Save before closing?"));
360                 }
361                 else {
362                         const char *basename = BLI_path_basename(BKE_main_blendfile_path(bmain));
363                         message = BLI_sprintfN(IFACE_("Save changes to \"%s\" before closing?"), basename);
364                 }
365                 uiItemL(layout, message, ICON_ERROR);
366                 MEM_freeN(message);
367         }
368
369         uiItemS(layout);
370         uiItemS(layout);
371
372
373         /* Buttons */
374         uiBut *but;
375
376         uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
377
378         uiLayout *col = uiLayoutColumn(split, false);
379
380         but = uiDefIconTextBut(
381                 block, UI_BTYPE_BUT, 0, ICON_SCREEN_BACK, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y,
382                 NULL, 0, 0, 0, 0, TIP_("Do not quit"));
383         UI_but_func_set(but, wm_block_confirm_quit_cancel, block, NULL);
384
385         /* empty space between buttons */
386         col = uiLayoutColumn(split, false);
387         uiItemS(col);
388
389         col = uiLayoutColumn(split, 1);
390         but = uiDefIconTextBut(
391                 block, UI_BTYPE_BUT, 0, ICON_CANCEL, IFACE_("Discard Changes"), 0, 0, 50, UI_UNIT_Y,
392                 NULL, 0, 0, 0, 0, TIP_("Discard changes and quit"));
393         UI_but_func_set(but, wm_block_confirm_quit_discard, block, NULL);
394
395         col = uiLayoutColumn(split, 1);
396         but = uiDefIconTextBut(
397                 block, UI_BTYPE_BUT, 0, ICON_FILE_TICK, IFACE_("Save & Quit"), 0, 0, 50, UI_UNIT_Y,
398                 NULL, 0, 0, 0, 0, TIP_("Save and quit"));
399         UI_but_func_set(but, wm_block_confirm_quit_save, block, NULL);
400
401         UI_block_bounds_set_centered(block, 10);
402
403         return block;
404 }
405
406
407 /**
408  * Call the confirm dialog on quitting. It's displayed in the context window so
409  * caller should set it as desired.
410  */
411 static void wm_confirm_quit(bContext *C)
412 {
413         wmWindow *win = CTX_wm_window(C);
414
415         if (GHOST_SupportsNativeDialogs() == 0) {
416                 UI_popup_block_invoke(C, block_create_confirm_quit, NULL);
417         }
418         else if (GHOST_confirmQuit(win->ghostwin)) {
419                 wm_exit_schedule_delayed(C);
420         }
421 }
422
423 /**
424  * Call the quit confirmation prompt or exit directly if needed. The use can
425  * still cancel via the confirmation popup. Also, this may not quit Blender
426  * immediately, but rather schedule the closing.
427  *
428  * \param win: The window to show the confirmation popup/window in.
429  */
430 void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win)
431 {
432         wmWindowManager *wm = CTX_wm_manager(C);
433         wmWindow *win_ctx = CTX_wm_window(C);
434
435         /* The popup will be displayed in the context window which may not be set
436          * here (this function gets called outside of normal event handling loop). */
437         CTX_wm_window_set(C, win);
438
439         if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved && !G.background) {
440                 wm_confirm_quit(C);
441         }
442         else {
443                 wm_exit_schedule_delayed(C);
444         }
445
446         CTX_wm_window_set(C, win_ctx);
447 }
448
449 /** \} */
450
451 /* this is event from ghost, or exit-blender op */
452 void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
453 {
454         wmWindow *tmpwin;
455
456         /* first check if we have to quit (there are non-temp remaining windows) */
457         for (tmpwin = wm->windows.first; tmpwin; tmpwin = tmpwin->next) {
458                 if (tmpwin == win)
459                         continue;
460                 if (tmpwin->screen->temp == 0)
461                         break;
462         }
463
464         if (tmpwin == NULL) {
465                 wm_quit_with_optional_confirmation_prompt(C, win);
466         }
467         else {
468                 /* We're just closing a window */
469                 bScreen *screen = win->screen;
470
471                 BLI_remlink(&wm->windows, win);
472
473                 wm_draw_window_clear(win);
474
475                 CTX_wm_window_set(C, win);  /* needed by handlers */
476                 WM_event_remove_handlers(C, &win->handlers);
477                 WM_event_remove_handlers(C, &win->modalhandlers);
478
479                 /* for regular use this will _never_ be NULL,
480                  * however we may be freeing an improperly initialized window. */
481                 if (win->screen) {
482                         ED_screen_exit(C, win, win->screen);
483                 }
484
485                 wm_window_free(C, wm, win);
486
487                 /* if temp screen, delete it after window free (it stops jobs that can access it) */
488                 if (screen && screen->temp) {
489                         Main *bmain = CTX_data_main(C);
490                         BKE_libblock_free(bmain, screen);
491                 }
492         }
493 }
494
495 void wm_window_title(wmWindowManager *wm, wmWindow *win)
496 {
497         if (win->screen && win->screen->temp) {
498                 /* nothing to do for 'temp' windows,
499                  * because WM_window_open_temp always sets window title  */
500         }
501         else if (win->ghostwin) {
502                 /* this is set to 1 if you don't have startup.blend open */
503                 if (G.save_over && BKE_main_blendfile_path_from_global()[0]) {
504                         char str[sizeof(((Main *)NULL)->name) + 24];
505                         BLI_snprintf(str, sizeof(str), "Blender%s [%s%s]", wm->file_saved ? "" : "*",
506                                      BKE_main_blendfile_path_from_global(),
507                                      G_MAIN->recovered ? " (Recovered)" : "");
508                         GHOST_SetTitle(win->ghostwin, str);
509                 }
510                 else
511                         GHOST_SetTitle(win->ghostwin, "Blender");
512
513                 /* Informs GHOST of unsaved changes, to set window modified visual indicator (MAC OS X)
514                  * and to give hint of unsaved changes for a user warning mechanism
515                  * in case of OS application terminate request (e.g. OS Shortcut Alt+F4, Cmd+Q, (...), or session end) */
516                 GHOST_SetWindowModifiedState(win->ghostwin, (GHOST_TUns8) !wm->file_saved);
517
518         }
519 }
520
521 void WM_window_set_dpi(wmWindow *win)
522 {
523         float auto_dpi = GHOST_GetDPIHint(win->ghostwin);
524
525         /* Clamp auto DPI to 96, since our font/interface drawing does not work well
526          * with lower sizes. The main case we are interested in supporting is higher
527          * DPI. If a smaller UI is desired it is still possible to adjust UI scale. */
528         auto_dpi = max_ff(auto_dpi, 96.0f);
529
530         /* Lazily init UI scale size, preserving backwards compatibility by
531          * computing UI scale from ratio of previous DPI and auto DPI */
532         if (U.ui_scale == 0) {
533                 int virtual_pixel = (U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1 : 2;
534
535                 if (U.dpi == 0) {
536                         U.ui_scale = virtual_pixel;
537                 }
538                 else {
539                         U.ui_scale = (virtual_pixel * U.dpi * 96.0f) / (auto_dpi * 72.0f);
540                 }
541
542                 CLAMP(U.ui_scale, 0.25f, 4.0f);
543         }
544
545         /* Blender's UI drawing assumes DPI 72 as a good default following macOS
546          * while Windows and Linux use DPI 96. GHOST assumes a default 96 so we
547          * remap the DPI to Blender's convention. */
548         auto_dpi *= GHOST_GetNativePixelSize(win->ghostwin);
549         int dpi = auto_dpi * U.ui_scale * (72.0 / 96.0f);
550
551         /* Automatically set larger pixel size for high DPI. */
552         int pixelsize = max_ii(1, (int)(dpi / 64));
553         /* User adjustment for pixel size. */
554         pixelsize = max_ii(1, pixelsize + U.ui_line_width);
555
556         /* Set user preferences globals for drawing, and for forward compatibility. */
557         U.pixelsize = pixelsize;
558         U.dpi = dpi / pixelsize;
559         U.virtual_pixel = (pixelsize == 1) ? VIRTUAL_PIXEL_NATIVE : VIRTUAL_PIXEL_DOUBLE;
560         U.widget_unit = (U.pixelsize * U.dpi * 20 + 36) / 72;
561
562         /* update font drawing */
563         BLF_default_dpi(U.pixelsize * U.dpi);
564 }
565
566 /* belongs to below */
567 static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wmWindow *win)
568 {
569         GHOST_WindowHandle ghostwin;
570         GHOST_GLSettings glSettings = {0};
571         static int multisamples = -1;
572         int scr_w, scr_h, posy;
573
574         /* force setting multisamples only once, it requires restart - and you cannot
575          * mix it, either all windows have it, or none (tested in OSX opengl) */
576         if (multisamples == -1)
577                 multisamples = U.ogl_multisamples;
578
579         glSettings.numOfAASamples = multisamples;
580
581         /* a new window is created when pageflip mode is required for a window */
582         if (win->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP)
583                 glSettings.flags |= GHOST_glStereoVisual;
584
585         if (G.debug & G_DEBUG_GPU) {
586                 glSettings.flags |= GHOST_glDebugContext;
587         }
588
589         wm_get_screensize(&scr_w, &scr_h);
590         posy = (scr_h - win->posy - win->sizey);
591
592         ghostwin = GHOST_CreateWindow(g_system, title,
593                                       win->posx, posy, win->sizex, win->sizey,
594                                       (GHOST_TWindowState)win->windowstate,
595                                       GHOST_kDrawingContextTypeOpenGL,
596                                       glSettings);
597
598         if (ghostwin) {
599                 GHOST_RectangleHandle bounds;
600
601                 /* the new window has already been made drawable upon creation */
602                 wm->windrawable = win;
603
604                 /* needed so we can detect the graphics card below */
605                 GPU_init();
606
607                 win->ghostwin = ghostwin;
608                 GHOST_SetWindowUserData(ghostwin, win); /* pointer back */
609
610                 if (win->eventstate == NULL)
611                         win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
612
613                 /* store multisamples window was created with, in case user prefs change */
614                 win->multisamples = multisamples;
615
616                 /* store actual window size in blender window */
617                 bounds = GHOST_GetClientBounds(win->ghostwin);
618
619                 /* win32: gives undefined window size when minimized */
620                 if (GHOST_GetWindowState(win->ghostwin) != GHOST_kWindowStateMinimized) {
621                         win->sizex = GHOST_GetWidthRectangle(bounds);
622                         win->sizey = GHOST_GetHeightRectangle(bounds);
623                 }
624                 GHOST_DisposeRectangle(bounds);
625
626 #ifndef __APPLE__
627                 /* set the state here, so minimized state comes up correct on windows */
628                 if (wm_init_state.window_focus) {
629                         GHOST_SetWindowState(ghostwin, (GHOST_TWindowState)win->windowstate);
630                 }
631 #endif
632                 /* until screens get drawn, make it nice gray */
633                 glClearColor(0.55, 0.55, 0.55, 0.0);
634                 /* Crash on OSS ATI: bugs.launchpad.net/ubuntu/+source/mesa/+bug/656100 */
635                 if (!GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE)) {
636                         glClear(GL_COLOR_BUFFER_BIT);
637                 }
638
639                 /* needed here, because it's used before it reads userdef */
640                 WM_window_set_dpi(win);
641
642                 wm_window_swap_buffers(win);
643
644                 //GHOST_SetWindowState(ghostwin, GHOST_kWindowStateModified);
645
646                 /* standard state vars for window */
647                 glEnable(GL_SCISSOR_TEST);
648                 GPU_state_init();
649         }
650 }
651
652 /**
653  * Initialize #wmWindow without ghostwin, open these and clear.
654  *
655  * window size is read from window, if 0 it uses prefsize
656  * called in #WM_check, also inits stuff after file read.
657  *
658  * \warning
659  * After running, 'win->ghostwin' can be NULL in rare cases
660  * (where OpenGL driver fails to create a context for eg).
661  * We could remove them with #wm_window_ghostwindows_remove_invalid
662  * but better not since caller may continue to use.
663  * Instead, caller needs to handle the error case and cleanup.
664  */
665 void wm_window_ghostwindows_ensure(wmWindowManager *wm)
666 {
667         wmKeyMap *keymap;
668         wmWindow *win;
669
670         BLI_assert(G.background == false);
671
672         /* no commandline prefsize? then we set this.
673          * Note that these values will be used only
674          * when there is no startup.blend yet.
675          */
676         if (wm_init_state.size_x == 0) {
677                 wm_get_screensize(&wm_init_state.size_x, &wm_init_state.size_y);
678
679                 /* note!, this isnt quite correct, active screen maybe offset 1000s if PX,
680                  * we'd need a wm_get_screensize like function that gives offset,
681                  * in practice the window manager will likely move to the correct monitor */
682                 wm_init_state.start_x = 0;
683                 wm_init_state.start_y = 0;
684
685 #ifdef WITH_X11 /* X11 */
686                 /* X11, start maximized but use default sane size */
687                 wm_init_state.size_x = min_ii(wm_init_state.size_x, WM_WIN_INIT_SIZE_X);
688                 wm_init_state.size_y = min_ii(wm_init_state.size_y, WM_WIN_INIT_SIZE_Y);
689                 /* pad */
690                 wm_init_state.start_x = WM_WIN_INIT_PAD;
691                 wm_init_state.start_y = WM_WIN_INIT_PAD;
692                 wm_init_state.size_x -= WM_WIN_INIT_PAD * 2;
693                 wm_init_state.size_y -= WM_WIN_INIT_PAD * 2;
694 #endif
695         }
696
697         for (win = wm->windows.first; win; win = win->next) {
698                 if (win->ghostwin == NULL) {
699                         if ((win->sizex == 0) || (wm_init_state.override_flag & WIN_OVERRIDE_GEOM)) {
700                                 win->posx = wm_init_state.start_x;
701                                 win->posy = wm_init_state.start_y;
702                                 win->sizex = wm_init_state.size_x;
703                                 win->sizey = wm_init_state.size_y;
704
705                                 win->windowstate = GHOST_kWindowStateNormal;
706                                 wm_init_state.override_flag &= ~WIN_OVERRIDE_GEOM;
707                         }
708
709                         if (wm_init_state.override_flag & WIN_OVERRIDE_WINSTATE) {
710                                 win->windowstate = wm_init_state.windowstate;
711                                 wm_init_state.override_flag &= ~WIN_OVERRIDE_WINSTATE;
712                         }
713
714                         /* without this, cursor restore may fail, T45456 */
715                         if (win->cursor == 0) {
716                                 win->cursor = CURSOR_STD;
717                         }
718
719                         wm_window_ghostwindow_add(wm, "Blender", win);
720                 }
721                 /* happens after fileread */
722                 if (win->eventstate == NULL)
723                         win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
724
725                 /* add keymap handlers (1 handler for all keys in map!) */
726                 keymap = WM_keymap_ensure(wm->defaultconf, "Window", 0, 0);
727                 WM_event_add_keymap_handler(&win->handlers, keymap);
728
729                 keymap = WM_keymap_ensure(wm->defaultconf, "Screen", 0, 0);
730                 WM_event_add_keymap_handler(&win->handlers, keymap);
731
732                 keymap = WM_keymap_ensure(wm->defaultconf, "Screen Editing", 0, 0);
733                 WM_event_add_keymap_handler(&win->modalhandlers, keymap);
734
735                 /* add drop boxes */
736                 {
737                         ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
738                         WM_event_add_dropbox_handler(&win->handlers, lb);
739                 }
740                 wm_window_title(wm, win);
741         }
742 }
743
744 /**
745  * Call after #wm_window_ghostwindows_ensure or #WM_check
746  * (after loading a new file) in the unlikely event a window couldn't be created.
747  */
748 void wm_window_ghostwindows_remove_invalid(bContext *C, wmWindowManager *wm)
749 {
750         wmWindow *win, *win_next;
751
752         BLI_assert(G.background == false);
753
754         for (win = wm->windows.first; win; win = win_next) {
755                 win_next = win->next;
756                 if (win->ghostwin == NULL) {
757                         wm_window_close(C, wm, win);
758                 }
759         }
760 }
761
762 /**
763  * new window, no screen yet, but we open ghostwindow for it,
764  * also gets the window level handlers
765  * \note area-rip calls this.
766  * \return the window or NULL.
767  */
768 wmWindow *WM_window_open(bContext *C, const rcti *rect)
769 {
770         wmWindow *win_prev = CTX_wm_window(C);
771         wmWindow *win = wm_window_new(C);
772
773         win->posx = rect->xmin;
774         win->posy = rect->ymin;
775         win->sizex = BLI_rcti_size_x(rect);
776         win->sizey = BLI_rcti_size_y(rect);
777
778         win->drawmethod = U.wmdrawmethod;
779
780         WM_check(C);
781
782         if (win->ghostwin) {
783                 return win;
784         }
785         else {
786                 wm_window_close(C, CTX_wm_manager(C), win);
787                 CTX_wm_window_set(C, win_prev);
788                 return NULL;
789         }
790 }
791
792 /**
793  * Uses `screen->temp` tag to define what to do, currently it limits
794  * to only one "temp" window for render out, preferences, filewindow, etc...
795  *
796  * \param type: WM_WINDOW_RENDER, WM_WINDOW_USERPREFS...
797  * \return the window or NULL.
798  */
799 wmWindow *WM_window_open_temp(bContext *C, int x, int y, int sizex, int sizey, int type)
800 {
801         Main *bmain = CTX_data_main(C);
802         wmWindow *win_prev = CTX_wm_window(C);
803         wmWindow *win;
804         ScrArea *sa;
805         Scene *scene = CTX_data_scene(C);
806         const char *title;
807
808         /* convert to native OS window coordinates */
809         const float native_pixel_size = GHOST_GetNativePixelSize(win_prev->ghostwin);
810         x /= native_pixel_size;
811         y /= native_pixel_size;
812         sizex /= native_pixel_size;
813         sizey /= native_pixel_size;
814
815         /* calculate position */
816         rcti rect;
817         rect.xmin = x + win_prev->posx - sizex / 2;
818         rect.ymin = y + win_prev->posy - sizey / 2;
819         rect.xmax = rect.xmin + sizex;
820         rect.ymax = rect.ymin + sizey;
821
822         /* changes rect to fit within desktop */
823         wm_window_check_position(&rect);
824
825         /* test if we have a temp screen already */
826         for (win = CTX_wm_manager(C)->windows.first; win; win = win->next)
827                 if (win->screen->temp)
828                         break;
829
830         /* add new window? */
831         if (win == NULL) {
832                 win = wm_window_new(C);
833
834                 win->posx = rect.xmin;
835                 win->posy = rect.ymin;
836         }
837
838         win->sizex = BLI_rcti_size_x(&rect);
839         win->sizey = BLI_rcti_size_y(&rect);
840
841         if (win->ghostwin) {
842                 wm_window_set_size(win, win->sizex, win->sizey);
843                 wm_window_raise(win);
844         }
845
846         if (win->screen == NULL) {
847                 /* add new screen */
848                 win->screen = ED_screen_add(bmain, win, scene, "temp");
849         }
850         else {
851                 /* switch scene for rendering */
852                 if (win->screen->scene != scene)
853                         ED_screen_set_scene(C, win->screen, scene);
854         }
855
856         win->screen->temp = 1;
857
858         /* make window active, and validate/resize */
859         CTX_wm_window_set(C, win);
860         WM_check(C);
861
862         /* It's possible `win->ghostwin == NULL`.
863          * instead of attempting to cleanup here (in a half finished state),
864          * finish setting up the screen, then free it at the end of the function,
865          * to avoid having to take into account a partially-created window.
866          */
867
868         /* ensure it shows the right spacetype editor */
869         sa = win->screen->areabase.first;
870         CTX_wm_area_set(C, sa);
871
872         if (type == WM_WINDOW_RENDER) {
873                 ED_area_newspace(C, sa, SPACE_IMAGE, false);
874         }
875         else {
876                 ED_area_newspace(C, sa, SPACE_USERPREF, false);
877         }
878
879         ED_screen_set(C, win->screen);
880         ED_screen_refresh(CTX_wm_manager(C), win); /* test scale */
881
882         if (sa->spacetype == SPACE_IMAGE)
883                 title = IFACE_("Blender Render");
884         else if (ELEM(sa->spacetype, SPACE_OUTLINER, SPACE_USERPREF))
885                 title = IFACE_("Blender User Preferences");
886         else if (sa->spacetype == SPACE_FILE)
887                 title = IFACE_("Blender File View");
888         else
889                 title = "Blender";
890
891         if (win->ghostwin) {
892                 GHOST_SetTitle(win->ghostwin, title);
893                 return win;
894         }
895         else {
896                 /* very unlikely! but opening a new window can fail */
897                 wm_window_close(C, CTX_wm_manager(C), win);
898                 CTX_wm_window_set(C, win_prev);
899
900                 return NULL;
901         }
902 }
903
904
905 /* ****************** Operators ****************** */
906
907 int wm_window_close_exec(bContext *C, wmOperator *UNUSED(op))
908 {
909         wmWindowManager *wm = CTX_wm_manager(C);
910         wmWindow *win = CTX_wm_window(C);
911         wm_window_close(C, wm, win);
912         return OPERATOR_FINISHED;
913 }
914
915 /* operator callback */
916 int wm_window_duplicate_exec(bContext *C, wmOperator *UNUSED(op))
917 {
918         wmWindow *win_src = CTX_wm_window(C);
919         bool ok;
920
921         ok = (wm_window_copy_test(C, win_src) != NULL);
922
923         return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
924 }
925
926
927 /* fullscreen operator callback */
928 int wm_window_fullscreen_toggle_exec(bContext *C, wmOperator *UNUSED(op))
929 {
930         wmWindow *window = CTX_wm_window(C);
931         GHOST_TWindowState state;
932
933         if (G.background)
934                 return OPERATOR_CANCELLED;
935
936         state = GHOST_GetWindowState(window->ghostwin);
937         if (state != GHOST_kWindowStateFullScreen)
938                 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateFullScreen);
939         else
940                 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateNormal);
941
942         return OPERATOR_FINISHED;
943
944 }
945
946
947 /* ************ events *************** */
948
949 void wm_cursor_position_from_ghost(wmWindow *win, int *x, int *y)
950 {
951         float fac = GHOST_GetNativePixelSize(win->ghostwin);
952
953         GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
954         *x *= fac;
955
956         *y = (win->sizey - 1) - *y;
957         *y *= fac;
958 }
959
960 void wm_cursor_position_to_ghost(wmWindow *win, int *x, int *y)
961 {
962         float fac = GHOST_GetNativePixelSize(win->ghostwin);
963
964         *x /= fac;
965         *y /= fac;
966         *y = win->sizey - *y - 1;
967
968         GHOST_ClientToScreen(win->ghostwin, *x, *y, x, y);
969 }
970
971 void wm_get_cursor_position(wmWindow *win, int *x, int *y)
972 {
973         GHOST_GetCursorPosition(g_system, x, y);
974         wm_cursor_position_from_ghost(win, x, y);
975 }
976
977 typedef enum {
978         SHIFT    = 's',
979         CONTROL  = 'c',
980         ALT      = 'a',
981         OS       = 'C'
982 } modifierKeyType;
983
984 /* check if specified modifier key type is pressed */
985 static int query_qual(modifierKeyType qual)
986 {
987         GHOST_TModifierKeyMask left, right;
988         int val = 0;
989
990         switch (qual) {
991                 case SHIFT:
992                         left = GHOST_kModifierKeyLeftShift;
993                         right = GHOST_kModifierKeyRightShift;
994                         break;
995                 case CONTROL:
996                         left = GHOST_kModifierKeyLeftControl;
997                         right = GHOST_kModifierKeyRightControl;
998                         break;
999                 case OS:
1000                         left = right = GHOST_kModifierKeyOS;
1001                         break;
1002                 case ALT:
1003                 default:
1004                         left = GHOST_kModifierKeyLeftAlt;
1005                         right = GHOST_kModifierKeyRightAlt;
1006                         break;
1007         }
1008
1009         GHOST_GetModifierKeyState(g_system, left, &val);
1010         if (!val)
1011                 GHOST_GetModifierKeyState(g_system, right, &val);
1012
1013         return val;
1014 }
1015
1016 void wm_window_make_drawable(wmWindowManager *wm, wmWindow *win)
1017 {
1018         if (win != wm->windrawable && win->ghostwin) {
1019 //              win->lmbut = 0; /* keeps hanging when mousepressed while other window opened */
1020
1021                 wm->windrawable = win;
1022                 if (G.debug & G_DEBUG_EVENTS) {
1023                         printf("%s: set drawable %d\n", __func__, win->winid);
1024                 }
1025                 GHOST_ActivateWindowDrawingContext(win->ghostwin);
1026
1027                 /* this can change per window */
1028                 WM_window_set_dpi(win);
1029         }
1030 }
1031
1032 /* called by ghost, here we handle events for windows themselves or send to event system */
1033 /* mouse coordinate converversion happens here */
1034 static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr)
1035 {
1036         bContext *C = C_void_ptr;
1037         wmWindowManager *wm = CTX_wm_manager(C);
1038         GHOST_TEventType type = GHOST_GetEventType(evt);
1039         int time = GHOST_GetEventTime(evt);
1040
1041         if (type == GHOST_kEventQuit) {
1042                 WM_exit(C);
1043         }
1044         else {
1045                 GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
1046                 GHOST_TEventDataPtr data = GHOST_GetEventData(evt);
1047                 wmWindow *win;
1048
1049                 /* Ghost now can call this function for life resizes, but it should return if WM didn't initialize yet.
1050                  * Can happen on file read (especially full size window)  */
1051                 if ((wm->initialized & WM_INIT_WINDOW) == 0) {
1052                         return 1;
1053                 }
1054                 if (!ghostwin) {
1055                         /* XXX - should be checked, why are we getting an event here, and */
1056                         /* what is it? */
1057                         puts("<!> event has no window");
1058                         return 1;
1059                 }
1060                 else if (!GHOST_ValidWindow(g_system, ghostwin)) {
1061                         /* XXX - should be checked, why are we getting an event here, and */
1062                         /* what is it? */
1063                         puts("<!> event has invalid window");
1064                         return 1;
1065                 }
1066                 else {
1067                         win = GHOST_GetWindowUserData(ghostwin);
1068                 }
1069
1070                 switch (type) {
1071                         case GHOST_kEventWindowDeactivate:
1072                                 wm_event_add_ghostevent(wm, win, type, time, data);
1073                                 win->active = 0; /* XXX */
1074
1075                                 /* clear modifiers for inactive windows */
1076                                 win->eventstate->alt = 0;
1077                                 win->eventstate->ctrl = 0;
1078                                 win->eventstate->shift = 0;
1079                                 win->eventstate->oskey = 0;
1080                                 win->eventstate->keymodifier = 0;
1081
1082                                 break;
1083                         case GHOST_kEventWindowActivate:
1084                         {
1085                                 GHOST_TEventKeyData kdata;
1086                                 wmEvent event;
1087                                 int wx, wy;
1088                                 const int keymodifier = ((query_qual(SHIFT)     ? KM_SHIFT : 0) |
1089                                                          (query_qual(CONTROL)   ? KM_CTRL  : 0) |
1090                                                          (query_qual(ALT)       ? KM_ALT   : 0) |
1091                                                          (query_qual(OS)        ? KM_OSKEY : 0));
1092
1093                                 /* Win23/GHOST modifier bug, see T40317 */
1094 #ifndef WIN32
1095 //#  define USE_WIN_ACTIVATE
1096 #endif
1097
1098                                 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
1099
1100                                 win->active = 1;
1101 //                              window_handle(win, INPUTCHANGE, win->active);
1102
1103                                 /* bad ghost support for modifier keys... so on activate we set the modifiers again */
1104
1105                                 /* TODO: This is not correct since a modifier may be held when a window is activated...
1106                                  * better solve this at ghost level. attempted fix r54450 but it caused bug [#34255]
1107                                  *
1108                                  * For now don't send GHOST_kEventKeyDown events, just set the 'eventstate'.
1109                                  */
1110                                 kdata.ascii = '\0';
1111                                 kdata.utf8_buf[0] = '\0';
1112
1113                                 if (win->eventstate->shift) {
1114                                         if ((keymodifier & KM_SHIFT) == 0) {
1115                                                 kdata.key = GHOST_kKeyLeftShift;
1116                                                 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1117                                         }
1118                                 }
1119 #ifdef USE_WIN_ACTIVATE
1120                                 else {
1121                                         if (keymodifier & KM_SHIFT) {
1122                                                 win->eventstate->shift = KM_MOD_FIRST;
1123                                         }
1124                                 }
1125 #endif
1126                                 if (win->eventstate->ctrl) {
1127                                         if ((keymodifier & KM_CTRL) == 0) {
1128                                                 kdata.key = GHOST_kKeyLeftControl;
1129                                                 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1130                                         }
1131                                 }
1132 #ifdef USE_WIN_ACTIVATE
1133                                 else {
1134                                         if (keymodifier & KM_CTRL) {
1135                                                 win->eventstate->ctrl = KM_MOD_FIRST;
1136                                         }
1137                                 }
1138 #endif
1139                                 if (win->eventstate->alt) {
1140                                         if ((keymodifier & KM_ALT) == 0) {
1141                                                 kdata.key = GHOST_kKeyLeftAlt;
1142                                                 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1143                                         }
1144                                 }
1145 #ifdef USE_WIN_ACTIVATE
1146                                 else {
1147                                         if (keymodifier & KM_ALT) {
1148                                                 win->eventstate->alt = KM_MOD_FIRST;
1149                                         }
1150                                 }
1151 #endif
1152                                 if (win->eventstate->oskey) {
1153                                         if ((keymodifier & KM_OSKEY) == 0) {
1154                                                 kdata.key = GHOST_kKeyOS;
1155                                                 wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
1156                                         }
1157                                 }
1158 #ifdef USE_WIN_ACTIVATE
1159                                 else {
1160                                         if (keymodifier & KM_OSKEY) {
1161                                                 win->eventstate->oskey = KM_MOD_FIRST;
1162                                         }
1163                                 }
1164 #endif
1165
1166 #undef USE_WIN_ACTIVATE
1167
1168
1169                                 /* keymodifier zero, it hangs on hotkeys that open windows otherwise */
1170                                 win->eventstate->keymodifier = 0;
1171
1172                                 /* entering window, update mouse pos. but no event */
1173                                 wm_get_cursor_position(win,  &wx, &wy);
1174
1175                                 win->eventstate->x = wx;
1176                                 win->eventstate->y = wy;
1177
1178                                 win->addmousemove = 1;   /* enables highlighted buttons */
1179
1180                                 wm_window_make_drawable(wm, win);
1181
1182                                 /* window might be focused by mouse click in configuration of window manager
1183                                  * when focus is not following mouse
1184                                  * click could have been done on a button and depending on window manager settings
1185                                  * click would be passed to blender or not, but in any case button under cursor
1186                                  * should be activated, so at max next click on button without moving mouse
1187                                  * would trigger it's handle function
1188                                  * currently it seems to be common practice to generate new event for, but probably
1189                                  * we'll need utility function for this? (sergey)
1190                                  */
1191                                 wm_event_init_from_window(win, &event);
1192                                 event.type = MOUSEMOVE;
1193                                 event.prevx = event.x;
1194                                 event.prevy = event.y;
1195
1196                                 wm_event_add(win, &event);
1197
1198                                 break;
1199                         }
1200                         case GHOST_kEventWindowClose:
1201                         {
1202                                 wm_window_close(C, wm, win);
1203                                 break;
1204                         }
1205                         case GHOST_kEventWindowUpdate:
1206                         {
1207                                 if (G.debug & G_DEBUG_EVENTS) {
1208                                         printf("%s: ghost redraw %d\n", __func__, win->winid);
1209                                 }
1210
1211                                 wm_window_make_drawable(wm, win);
1212                                 WM_event_add_notifier(C, NC_WINDOW, NULL);
1213
1214                                 break;
1215                         }
1216                         case GHOST_kEventWindowSize:
1217                         case GHOST_kEventWindowMove:
1218                         {
1219                                 GHOST_TWindowState state;
1220                                 state = GHOST_GetWindowState(win->ghostwin);
1221                                 win->windowstate = state;
1222
1223                                 WM_window_set_dpi(win);
1224
1225                                 /* win32: gives undefined window size when minimized */
1226                                 if (state != GHOST_kWindowStateMinimized) {
1227                                         GHOST_RectangleHandle client_rect;
1228                                         int l, t, r, b, scr_w, scr_h;
1229                                         int sizex, sizey, posx, posy;
1230
1231                                         client_rect = GHOST_GetClientBounds(win->ghostwin);
1232                                         GHOST_GetRectangle(client_rect, &l, &t, &r, &b);
1233
1234                                         GHOST_DisposeRectangle(client_rect);
1235
1236                                         wm_get_desktopsize(&scr_w, &scr_h);
1237                                         sizex = r - l;
1238                                         sizey = b - t;
1239                                         posx = l;
1240                                         posy = scr_h - t - win->sizey;
1241
1242                                         /*
1243                                          * Ghost sometimes send size or move events when the window hasn't changed.
1244                                          * One case of this is using compiz on linux. To alleviate the problem
1245                                          * we ignore all such event here.
1246                                          *
1247                                          * It might be good to eventually do that at Ghost level, but that is for
1248                                          * another time.
1249                                          */
1250                                         if (win->sizex != sizex ||
1251                                             win->sizey != sizey ||
1252                                             win->posx != posx ||
1253                                             win->posy != posy)
1254                                         {
1255                                                 win->sizex = sizex;
1256                                                 win->sizey = sizey;
1257                                                 win->posx = posx;
1258                                                 win->posy = posy;
1259
1260                                                 /* debug prints */
1261                                                 if (G.debug & G_DEBUG_EVENTS) {
1262                                                         const char *state_str;
1263                                                         state = GHOST_GetWindowState(win->ghostwin);
1264
1265                                                         if (state == GHOST_kWindowStateNormal) {
1266                                                                 state_str = "normal";
1267                                                         }
1268                                                         else if (state == GHOST_kWindowStateMinimized) {
1269                                                                 state_str = "minimized";
1270                                                         }
1271                                                         else if (state == GHOST_kWindowStateMaximized) {
1272                                                                 state_str = "maximized";
1273                                                         }
1274                                                         else if (state == GHOST_kWindowStateFullScreen) {
1275                                                                 state_str = "fullscreen";
1276                                                         }
1277                                                         else {
1278                                                                 state_str = "<unknown>";
1279                                                         }
1280
1281                                                         printf("%s: window %d state = %s\n", __func__, win->winid, state_str);
1282
1283                                                         if (type != GHOST_kEventWindowSize) {
1284                                                                 printf("win move event pos %d %d size %d %d\n",
1285                                                                        win->posx, win->posy, win->sizex, win->sizey);
1286                                                         }
1287                                                 }
1288
1289                                                 wm_window_make_drawable(wm, win);
1290                                                 wm_draw_window_clear(win);
1291                                                 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1292                                                 WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1293
1294 #if defined(__APPLE__) || defined(WIN32)
1295                                                 /* OSX and Win32 don't return to the mainloop while resize */
1296                                                 wm_event_do_notifiers(C);
1297                                                 wm_draw_update(C);
1298
1299                                                 /* Warning! code above nulls 'C->wm.window', causing BGE to quit, see: T45699.
1300                                                  * Further, its easier to match behavior across platforms, so restore the window. */
1301                                                 CTX_wm_window_set(C, win);
1302 #endif
1303                                         }
1304                                 }
1305                                 break;
1306                         }
1307
1308                         case GHOST_kEventWindowDPIHintChanged:
1309                         {
1310                                 WM_window_set_dpi(win);
1311                                 /* font's are stored at each DPI level, without this we can easy load 100's of fonts */
1312                                 BLF_cache_clear();
1313
1314                                 WM_main_add_notifier(NC_WINDOW, NULL);      /* full redraw */
1315                                 WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL);    /* refresh region sizes */
1316                                 break;
1317                         }
1318
1319                         case GHOST_kEventOpenMainFile:
1320                         {
1321                                 PointerRNA props_ptr;
1322                                 wmWindow *oldWindow;
1323                                 const char *path = GHOST_GetEventData(evt);
1324
1325                                 if (path) {
1326                                         wmOperatorType *ot = WM_operatortype_find("WM_OT_open_mainfile", false);
1327                                         /* operator needs a valid window in context, ensures
1328                                          * it is correctly set */
1329                                         oldWindow = CTX_wm_window(C);
1330                                         CTX_wm_window_set(C, win);
1331
1332                                         WM_operator_properties_create_ptr(&props_ptr, ot);
1333                                         RNA_string_set(&props_ptr, "filepath", path);
1334                                         WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &props_ptr);
1335                                         WM_operator_properties_free(&props_ptr);
1336
1337                                         CTX_wm_window_set(C, oldWindow);
1338                                 }
1339                                 break;
1340                         }
1341                         case GHOST_kEventDraggingDropDone:
1342                         {
1343                                 wmEvent event;
1344                                 GHOST_TEventDragnDropData *ddd = GHOST_GetEventData(evt);
1345                                 int wx, wy;
1346
1347                                 /* entering window, update mouse pos */
1348                                 wm_get_cursor_position(win, &wx, &wy);
1349                                 win->eventstate->x = wx;
1350                                 win->eventstate->y = wy;
1351
1352                                 wm_event_init_from_window(win, &event);  /* copy last state, like mouse coords */
1353
1354                                 /* activate region */
1355                                 event.type = MOUSEMOVE;
1356                                 event.prevx = event.x;
1357                                 event.prevy = event.y;
1358
1359                                 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
1360                                 win->active = 1;
1361
1362                                 wm_event_add(win, &event);
1363
1364
1365                                 /* make blender drop event with custom data pointing to wm drags */
1366                                 event.type = EVT_DROP;
1367                                 event.val = KM_RELEASE;
1368                                 event.custom = EVT_DATA_DRAGDROP;
1369                                 event.customdata = &wm->drags;
1370                                 event.customdatafree = 1;
1371
1372                                 wm_event_add(win, &event);
1373
1374                                 /* printf("Drop detected\n"); */
1375
1376                                 /* add drag data to wm for paths: */
1377
1378                                 if (ddd->dataType == GHOST_kDragnDropTypeFilenames) {
1379                                         GHOST_TStringArray *stra = ddd->data;
1380                                         int a, icon;
1381
1382                                         for (a = 0; a < stra->count; a++) {
1383                                                 printf("drop file %s\n", stra->strings[a]);
1384                                                 /* try to get icon type from extension */
1385                                                 icon = ED_file_extension_icon((char *)stra->strings[a]);
1386
1387                                                 WM_event_start_drag(C, icon, WM_DRAG_PATH, stra->strings[a], 0.0, WM_DRAG_NOP);
1388                                                 /* void poin should point to string, it makes a copy */
1389                                                 break; /* only one drop element supported now */
1390                                         }
1391                                 }
1392
1393                                 break;
1394                         }
1395                         case GHOST_kEventNativeResolutionChange:
1396                         {
1397                                 // only update if the actual pixel size changes
1398                                 float prev_pixelsize = U.pixelsize;
1399                                 WM_window_set_dpi(win);
1400
1401                                 if (U.pixelsize != prev_pixelsize) {
1402                                         // close all popups since they are positioned with the pixel
1403                                         // size baked in and it's difficult to correct them
1404                                         wmWindow *oldWindow = CTX_wm_window(C);
1405                                         CTX_wm_window_set(C, win);
1406                                         UI_popup_handlers_remove_all(C, &win->modalhandlers);
1407                                         CTX_wm_window_set(C, oldWindow);
1408
1409                                         wm_window_make_drawable(wm, win);
1410                                         wm_draw_window_clear(win);
1411
1412                                         WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1413                                         WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
1414                                 }
1415
1416                                 break;
1417                         }
1418                         case GHOST_kEventTrackpad:
1419                         {
1420                                 GHOST_TEventTrackpadData *pd = data;
1421
1422                                 wm_cursor_position_from_ghost(win, &pd->x, &pd->y);
1423                                 wm_event_add_ghostevent(wm, win, type, time, data);
1424                                 break;
1425                         }
1426                         case GHOST_kEventCursorMove:
1427                         {
1428                                 GHOST_TEventCursorData *cd = data;
1429
1430                                 wm_cursor_position_from_ghost(win, &cd->x, &cd->y);
1431                                 wm_event_add_ghostevent(wm, win, type, time, data);
1432                                 break;
1433                         }
1434                         default:
1435                                 wm_event_add_ghostevent(wm, win, type, time, data);
1436                                 break;
1437                 }
1438
1439         }
1440         return 1;
1441 }
1442
1443
1444 /**
1445  * This timer system only gives maximum 1 timer event per redraw cycle,
1446  * to prevent queues to get overloaded.
1447  * Timer handlers should check for delta to decide if they just update, or follow real time.
1448  * Timer handlers can also set duration to match frames passed
1449  */
1450 static int wm_window_timer(const bContext *C)
1451 {
1452         wmWindowManager *wm = CTX_wm_manager(C);
1453         wmTimer *wt, *wtnext;
1454         wmWindow *win;
1455         double time = PIL_check_seconds_timer();
1456         int retval = 0;
1457
1458         for (wt = wm->timers.first; wt; wt = wtnext) {
1459                 wtnext = wt->next; /* in case timer gets removed */
1460                 win = wt->win;
1461
1462                 if (wt->sleep == 0) {
1463                         if (time > wt->ntime) {
1464                                 wt->delta = time - wt->ltime;
1465                                 wt->duration += wt->delta;
1466                                 wt->ltime = time;
1467                                 wt->ntime = wt->stime + wt->timestep * ceil(wt->duration / wt->timestep);
1468
1469                                 if (wt->event_type == TIMERJOBS)
1470                                         wm_jobs_timer(C, wm, wt);
1471                                 else if (wt->event_type == TIMERAUTOSAVE)
1472                                         wm_autosave_timer(C, wm, wt);
1473                                 else if (wt->event_type == TIMERNOTIFIER)
1474                                         WM_main_add_notifier(POINTER_AS_UINT(wt->customdata), NULL);
1475                                 else if (win) {
1476                                         wmEvent event;
1477                                         wm_event_init_from_window(win, &event);
1478
1479                                         event.type = wt->event_type;
1480                                         event.val = KM_NOTHING;
1481                                         event.keymodifier = 0;
1482                                         event.custom = EVT_DATA_TIMER;
1483                                         event.customdata = wt;
1484                                         wm_event_add(win, &event);
1485
1486                                         retval = 1;
1487                                 }
1488                         }
1489                 }
1490         }
1491         return retval;
1492 }
1493
1494 void wm_window_process_events(const bContext *C)
1495 {
1496         int hasevent;
1497
1498         BLI_assert(BLI_thread_is_main());
1499
1500         hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1501
1502         if (hasevent)
1503                 GHOST_DispatchEvents(g_system);
1504
1505         hasevent |= wm_window_timer(C);
1506
1507         /* no event, we sleep 5 milliseconds */
1508         if (hasevent == 0)
1509                 PIL_sleep_ms(5);
1510 }
1511
1512 void wm_window_process_events_nosleep(void)
1513 {
1514         if (GHOST_ProcessEvents(g_system, 0))
1515                 GHOST_DispatchEvents(g_system);
1516 }
1517
1518 /* exported as handle callback to bke blender.c */
1519 void wm_window_testbreak(void)
1520 {
1521         static double ltime = 0;
1522         double curtime = PIL_check_seconds_timer();
1523
1524         BLI_assert(BLI_thread_is_main());
1525
1526         /* only check for breaks every 50 milliseconds
1527          * if we get called more often.
1528          */
1529         if ((curtime - ltime) > 0.05) {
1530                 int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1531
1532                 if (hasevent)
1533                         GHOST_DispatchEvents(g_system);
1534
1535                 ltime = curtime;
1536         }
1537 }
1538
1539 /* **************** init ********************** */
1540
1541 void wm_ghost_init(bContext *C)
1542 {
1543         if (!g_system) {
1544                 GHOST_EventConsumerHandle consumer = GHOST_CreateEventConsumer(ghost_event_proc, C);
1545
1546                 g_system = GHOST_CreateSystem();
1547                 GHOST_AddEventConsumer(g_system, consumer);
1548
1549                 if (wm_init_state.native_pixels) {
1550                         GHOST_UseNativePixels();
1551                 }
1552
1553                 GHOST_UseWindowFocus(wm_init_state.window_focus);
1554         }
1555 }
1556
1557 void wm_ghost_exit(void)
1558 {
1559         if (g_system)
1560                 GHOST_DisposeSystem(g_system);
1561
1562         g_system = NULL;
1563 }
1564
1565 /* **************** timer ********************** */
1566
1567 /* to (de)activate running timers temporary */
1568 void WM_event_timer_sleep(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer, bool do_sleep)
1569 {
1570         wmTimer *wt;
1571
1572         for (wt = wm->timers.first; wt; wt = wt->next)
1573                 if (wt == timer)
1574                         break;
1575
1576         if (wt)
1577                 wt->sleep = do_sleep;
1578 }
1579
1580 wmTimer *WM_event_add_timer(wmWindowManager *wm, wmWindow *win, int event_type, double timestep)
1581 {
1582         wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1583
1584         wt->event_type = event_type;
1585         wt->ltime = PIL_check_seconds_timer();
1586         wt->ntime = wt->ltime + timestep;
1587         wt->stime = wt->ltime;
1588         wt->timestep = timestep;
1589         wt->win = win;
1590
1591         BLI_addtail(&wm->timers, wt);
1592
1593         return wt;
1594 }
1595
1596 wmTimer *WM_event_add_timer_notifier(wmWindowManager *wm, wmWindow *win, unsigned int type, double timestep)
1597 {
1598         wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1599
1600         wt->event_type = TIMERNOTIFIER;
1601         wt->ltime = PIL_check_seconds_timer();
1602         wt->ntime = wt->ltime + timestep;
1603         wt->stime = wt->ltime;
1604         wt->timestep = timestep;
1605         wt->win = win;
1606         wt->customdata = POINTER_FROM_UINT(type);
1607         wt->flags |= WM_TIMER_NO_FREE_CUSTOM_DATA;
1608
1609         BLI_addtail(&wm->timers, wt);
1610
1611         return wt;
1612 }
1613
1614 void WM_event_remove_timer(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer)
1615 {
1616         wmTimer *wt;
1617
1618         /* extra security check */
1619         for (wt = wm->timers.first; wt; wt = wt->next)
1620                 if (wt == timer)
1621                         break;
1622         if (wt) {
1623                 wmWindow *win;
1624
1625                 if (wm->reports.reporttimer == wt)
1626                         wm->reports.reporttimer = NULL;
1627
1628                 BLI_remlink(&wm->timers, wt);
1629                 if (wt->customdata != NULL && (wt->flags & WM_TIMER_NO_FREE_CUSTOM_DATA) == 0) {
1630                         MEM_freeN(wt->customdata);
1631                 }
1632                 MEM_freeN(wt);
1633
1634                 /* there might be events in queue with this timer as customdata */
1635                 for (win = wm->windows.first; win; win = win->next) {
1636                         wmEvent *event;
1637                         for (event = win->queue.first; event; event = event->next) {
1638                                 if (event->customdata == wt) {
1639                                         event->customdata = NULL;
1640                                         event->type = EVENT_NONE;       /* timer users customdata, dont want NULL == NULL */
1641                                 }
1642                         }
1643                 }
1644         }
1645 }
1646
1647 void WM_event_remove_timer_notifier(wmWindowManager *wm, wmWindow *win, wmTimer *timer)
1648 {
1649         timer->customdata = NULL;
1650         WM_event_remove_timer(wm, win, timer);
1651 }
1652
1653 /* ******************* clipboard **************** */
1654
1655 static char *wm_clipboard_text_get_ex(bool selection, int *r_len,
1656                                       bool firstline)
1657 {
1658         char *p, *p2, *buf, *newbuf;
1659
1660         if (G.background) {
1661                 *r_len = 0;
1662                 return NULL;
1663         }
1664
1665         buf = (char *)GHOST_getClipboard(selection);
1666         if (!buf) {
1667                 *r_len = 0;
1668                 return NULL;
1669         }
1670
1671         /* always convert from \r\n to \n */
1672         p2 = newbuf = MEM_mallocN(strlen(buf) + 1, __func__);
1673
1674         if (firstline) {
1675                 /* will return an over-alloc'ed value in the case there are newlines */
1676                 for (p = buf; *p; p++) {
1677                         if ((*p != '\n') && (*p != '\r')) {
1678                                 *(p2++) = *p;
1679                         }
1680                         else {
1681                                 break;
1682                         }
1683                 }
1684         }
1685         else {
1686                 for (p = buf; *p; p++) {
1687                         if (*p != '\r') {
1688                                 *(p2++) = *p;
1689                         }
1690                 }
1691         }
1692
1693         *p2 = '\0';
1694
1695         free(buf); /* ghost uses regular malloc */
1696
1697         *r_len = (p2 - newbuf);
1698
1699         return newbuf;
1700 }
1701
1702 /**
1703  * Return text from the clipboard.
1704  *
1705  * \note Caller needs to check for valid utf8 if this is a requirement.
1706  */
1707 char *WM_clipboard_text_get(bool selection, int *r_len)
1708 {
1709         return wm_clipboard_text_get_ex(selection, r_len, false);
1710 }
1711
1712 /**
1713  * Convenience function for pasting to areas of Blender which don't support newlines.
1714  */
1715 char *WM_clipboard_text_get_firstline(bool selection, int *r_len)
1716 {
1717         return wm_clipboard_text_get_ex(selection, r_len, true);
1718 }
1719
1720 void WM_clipboard_text_set(const char *buf, bool selection)
1721 {
1722         if (!G.background) {
1723 #ifdef _WIN32
1724                 /* do conversion from \n to \r\n on Windows */
1725                 const char *p;
1726                 char *p2, *newbuf;
1727                 int newlen = 0;
1728
1729                 for (p = buf; *p; p++) {
1730                         if (*p == '\n')
1731                                 newlen += 2;
1732                         else
1733                                 newlen++;
1734                 }
1735
1736                 newbuf = MEM_callocN(newlen + 1, "WM_clipboard_text_set");
1737
1738                 for (p = buf, p2 = newbuf; *p; p++, p2++) {
1739                         if (*p == '\n') {
1740                                 *(p2++) = '\r'; *p2 = '\n';
1741                         }
1742                         else {
1743                                 *p2 = *p;
1744                         }
1745                 }
1746                 *p2 = '\0';
1747
1748                 GHOST_putClipboard((GHOST_TInt8 *)newbuf, selection);
1749                 MEM_freeN(newbuf);
1750 #else
1751                 GHOST_putClipboard((GHOST_TInt8 *)buf, selection);
1752 #endif
1753         }
1754 }
1755
1756 /* ******************* progress bar **************** */
1757
1758 void WM_progress_set(wmWindow *win, float progress)
1759 {
1760         GHOST_SetProgressBar(win->ghostwin, progress);
1761 }
1762
1763 void WM_progress_clear(wmWindow *win)
1764 {
1765         GHOST_EndProgressBar(win->ghostwin);
1766 }
1767
1768 /* ************************************ */
1769
1770 void wm_window_get_position(wmWindow *win, int *r_pos_x, int *r_pos_y)
1771 {
1772         *r_pos_x = win->posx;
1773         *r_pos_y = win->posy;
1774 }
1775
1776 void wm_window_set_size(wmWindow *win, int width, int height)
1777 {
1778         GHOST_SetClientSize(win->ghostwin, width, height);
1779 }
1780
1781 void wm_window_lower(wmWindow *win)
1782 {
1783         GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderBottom);
1784 }
1785
1786 void wm_window_raise(wmWindow *win)
1787 {
1788         GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderTop);
1789 }
1790
1791 void wm_window_swap_buffers(wmWindow *win)
1792 {
1793
1794 #ifdef WIN32
1795         glDisable(GL_SCISSOR_TEST);
1796         GHOST_SwapWindowBuffers(win->ghostwin);
1797         glEnable(GL_SCISSOR_TEST);
1798 #else
1799         GHOST_SwapWindowBuffers(win->ghostwin);
1800 #endif
1801 }
1802
1803 void wm_window_set_swap_interval (wmWindow *win, int interval)
1804 {
1805         GHOST_SetSwapInterval(win->ghostwin, interval);
1806 }
1807
1808 bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut)
1809 {
1810         return GHOST_GetSwapInterval(win->ghostwin, intervalOut);
1811 }
1812
1813
1814 /* ******************* exported api ***************** */
1815
1816
1817 /* called whem no ghost system was initialized */
1818 void WM_init_state_size_set(int stax, int stay, int sizx, int sizy)
1819 {
1820         wm_init_state.start_x = stax; /* left hand pos */
1821         wm_init_state.start_y = stay; /* bottom pos */
1822         wm_init_state.size_x = sizx < 640 ? 640 : sizx;
1823         wm_init_state.size_y = sizy < 480 ? 480 : sizy;
1824         wm_init_state.override_flag |= WIN_OVERRIDE_GEOM;
1825 }
1826
1827 /* for borderless and border windows set from command-line */
1828 void WM_init_state_fullscreen_set(void)
1829 {
1830         wm_init_state.windowstate = GHOST_kWindowStateFullScreen;
1831         wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1832 }
1833
1834 void WM_init_state_normal_set(void)
1835 {
1836         wm_init_state.windowstate = GHOST_kWindowStateNormal;
1837         wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1838 }
1839
1840 void WM_init_window_focus_set(bool do_it)
1841 {
1842         wm_init_state.window_focus = do_it;
1843 }
1844
1845 void WM_init_native_pixels(bool do_it)
1846 {
1847         wm_init_state.native_pixels = do_it;
1848 }
1849
1850 /* This function requires access to the GHOST_SystemHandle (g_system) */
1851 void WM_cursor_warp(wmWindow *win, int x, int y)
1852 {
1853         if (win && win->ghostwin) {
1854                 int oldx = x, oldy = y;
1855
1856                 wm_cursor_position_to_ghost(win, &x, &y);
1857                 GHOST_SetCursorPosition(g_system, x, y);
1858
1859                 win->eventstate->prevx = oldx;
1860                 win->eventstate->prevy = oldy;
1861
1862                 win->eventstate->x = oldx;
1863                 win->eventstate->y = oldy;
1864         }
1865 }
1866
1867 /**
1868  * Set x, y to values we can actually position the cursor to.
1869  */
1870 void WM_cursor_compatible_xy(wmWindow *win, int *x, int *y)
1871 {
1872         float f = GHOST_GetNativePixelSize(win->ghostwin);
1873         if (f != 1.0f) {
1874                 *x = (int)(*x / f) * f;
1875                 *y = (int)(*y / f) * f;
1876         }
1877 }
1878
1879 /**
1880  * Get the cursor pressure, in most cases you'll want to use wmTabletData from the event
1881  */
1882 float WM_cursor_pressure(const struct wmWindow *win)
1883 {
1884         const GHOST_TabletData *td = GHOST_GetTabletData(win->ghostwin);
1885         /* if there's tablet data from an active tablet device then add it */
1886         if ((td != NULL) && td->Active != GHOST_kTabletModeNone) {
1887                 return td->Pressure;
1888         }
1889         else {
1890                 return -1.0f;
1891         }
1892 }
1893
1894 /* support for native pixel size */
1895 /* mac retina opens window in size X, but it has up to 2 x more pixels */
1896 int WM_window_pixels_x(wmWindow *win)
1897 {
1898         float f = GHOST_GetNativePixelSize(win->ghostwin);
1899
1900         return (int)(f * (float)win->sizex);
1901 }
1902
1903 int WM_window_pixels_y(wmWindow *win)
1904 {
1905         float f = GHOST_GetNativePixelSize(win->ghostwin);
1906
1907         return (int)(f * (float)win->sizey);
1908
1909 }
1910
1911 bool WM_window_is_fullscreen(wmWindow *win)
1912 {
1913         return win->windowstate == GHOST_kWindowStateFullScreen;
1914 }
1915
1916
1917 #ifdef WITH_INPUT_IME
1918 /* note: keep in mind wm_window_IME_begin is also used to reposition the IME window */
1919 void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete)
1920 {
1921         BLI_assert(win);
1922
1923         GHOST_BeginIME(win->ghostwin, x, win->sizey - y, w, h, complete);
1924 }
1925
1926 void wm_window_IME_end(wmWindow *win)
1927 {
1928         BLI_assert(win && win->ime_data);
1929
1930         GHOST_EndIME(win->ghostwin);
1931         win->ime_data = NULL;
1932 }
1933 #endif  /* WITH_INPUT_IME */