Mac HiDPI ("retina") handling:
[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
31
32 #include <math.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36
37 #include "DNA_listBase.h"       
38 #include "DNA_screen_types.h"
39 #include "DNA_windowmanager_types.h"
40 #include "RNA_access.h"
41
42 #include "MEM_guardedalloc.h"
43
44 #include "GHOST_C-api.h"
45
46 #include "BLI_blenlib.h"
47 #include "BLI_utildefines.h"
48
49 #include "BLF_translation.h"
50
51 #include "BKE_blender.h"
52 #include "BKE_context.h"
53 #include "BKE_library.h"
54 #include "BKE_global.h"
55 #include "BKE_main.h"
56
57
58 #include "BIF_gl.h"
59
60 #include "WM_api.h"
61 #include "WM_types.h"
62 #include "wm.h"
63 #include "wm_draw.h"
64 #include "wm_window.h"
65 #include "wm_subwindow.h"
66 #include "wm_event_system.h"
67
68 #include "ED_screen.h"
69 #include "ED_fileselect.h"
70
71 #include "PIL_time.h"
72
73 #include "GPU_draw.h"
74 #include "GPU_extensions.h"
75
76 #include "UI_interface.h"
77
78 /* the global to talk to ghost */
79 static GHOST_SystemHandle g_system = NULL;
80
81 typedef enum WinOverrideFlag {
82         WIN_OVERRIDE_GEOM     = (1 << 0),
83         WIN_OVERRIDE_WINSTATE = (1 << 1)
84 } WinOverrideFlag;
85
86 /* set by commandline */
87 static struct WMInitStruct {
88         /* window geometry */
89         int size_x, size_y;
90         int start_x, start_y;
91
92         int windowstate;
93         WinOverrideFlag override_flag;
94         
95         int native_pixels;
96 } wm_init_state = {0, 0, 0, 0, GHOST_kWindowStateNormal, 0, 1};
97
98 /* ******** win open & close ************ */
99
100 /* XXX this one should correctly check for apple top header...
101  * done for Cocoa : returns window contents (and not frame) max size*/
102 void wm_get_screensize(int *width_r, int *height_r)
103 {
104         unsigned int uiwidth;
105         unsigned int uiheight;
106         
107         GHOST_GetMainDisplayDimensions(g_system, &uiwidth, &uiheight);
108         *width_r = uiwidth;
109         *height_r = uiheight;
110 }
111
112 /* keeps offset and size within monitor bounds */
113 /* XXX solve dual screen... */
114 static void wm_window_check_position(rcti *rect)
115 {
116         int width, height, d;
117         
118         wm_get_screensize(&width, &height);
119         
120 #if defined(__APPLE__) && !defined(GHOST_COCOA)
121         height -= 70;
122 #endif
123         
124         if (rect->xmin < 0) {
125                 rect->xmax -= rect->xmin;
126                 rect->xmin  = 0;
127         }
128         if (rect->ymin < 0) {
129                 rect->ymax -= rect->ymin;
130                 rect->ymin  = 0;
131         }
132         if (rect->xmax > width) {
133                 d = rect->xmax - width;
134                 rect->xmax -= d;
135                 rect->xmin -= d;
136         }
137         if (rect->ymax > height) {
138                 d = rect->ymax - height;
139                 rect->ymax -= d;
140                 rect->ymin -= d;
141         }
142         
143         if (rect->xmin < 0) rect->xmin = 0;
144         if (rect->ymin < 0) rect->ymin = 0;
145 }
146
147
148 static void wm_ghostwindow_destroy(wmWindow *win) 
149 {
150         if (win->ghostwin) {
151                 GHOST_DisposeWindow(g_system, win->ghostwin);
152                 win->ghostwin = NULL;
153         }
154 }
155
156 /* including window itself, C can be NULL. 
157  * ED_screen_exit should have been called */
158 void wm_window_free(bContext *C, wmWindowManager *wm, wmWindow *win)
159 {
160         wmTimer *wt, *wtnext;
161         
162         /* update context */
163         if (C) {
164                 WM_event_remove_handlers(C, &win->handlers);
165                 WM_event_remove_handlers(C, &win->modalhandlers);
166
167                 if (CTX_wm_window(C) == win)
168                         CTX_wm_window_set(C, NULL);
169         }
170
171         /* always set drawable and active to NULL,
172          * prevents non-drawable state of main windows (bugs #22967 and #25071, possibly #22477 too) */
173         wm->windrawable = NULL;
174         wm->winactive = NULL;
175
176         /* end running jobs, a job end also removes its timer */
177         for (wt = wm->timers.first; wt; wt = wtnext) {
178                 wtnext = wt->next;
179                 if (wt->win == win && wt->event_type == TIMERJOBS)
180                         wm_jobs_timer_ended(wm, wt);
181         }
182         
183         /* timer removing, need to call this api function */
184         for (wt = wm->timers.first; wt; wt = wtnext) {
185                 wtnext = wt->next;
186                 if (wt->win == win)
187                         WM_event_remove_timer(wm, win, wt);
188         }
189
190         if (win->eventstate) MEM_freeN(win->eventstate);
191         
192         wm_event_free_all(win);
193         wm_subwindows_free(win);
194         
195         if (win->drawdata)
196                 MEM_freeN(win->drawdata);
197         
198         wm_ghostwindow_destroy(win);
199         
200         MEM_freeN(win);
201 }
202
203 static int find_free_winid(wmWindowManager *wm)
204 {
205         wmWindow *win;
206         int id = 1;
207         
208         for (win = wm->windows.first; win; win = win->next)
209                 if (id <= win->winid)
210                         id = win->winid + 1;
211         
212         return id;
213 }
214
215 /* don't change context itself */
216 wmWindow *wm_window_new(bContext *C)
217 {
218         wmWindowManager *wm = CTX_wm_manager(C);
219         wmWindow *win = MEM_callocN(sizeof(wmWindow), "window");
220         
221         BLI_addtail(&wm->windows, win);
222         win->winid = find_free_winid(wm);
223
224         return win;
225 }
226
227
228 /* part of wm_window.c api */
229 wmWindow *wm_window_copy(bContext *C, wmWindow *winorig)
230 {
231         wmWindow *win = wm_window_new(C);
232         
233         win->posx = winorig->posx + 10;
234         win->posy = winorig->posy;
235         win->sizex = winorig->sizex;
236         win->sizey = winorig->sizey;
237         
238         /* duplicate assigns to window */
239         win->screen = ED_screen_duplicate(win, winorig->screen);
240         BLI_strncpy(win->screenname, win->screen->id.name + 2, sizeof(win->screenname));
241         win->screen->winid = win->winid;
242
243         win->screen->do_refresh = TRUE;
244         win->screen->do_draw = TRUE;
245
246         win->drawmethod = U.wmdrawmethod;
247         win->drawdata = NULL;
248         
249         return win;
250 }
251
252 /* this is event from ghost, or exit-blender op */
253 void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
254 {
255         wmWindow *tmpwin;
256         int do_exit = 0;
257         
258         /* first check if we have to quit (there are non-temp remaining windows) */
259         for (tmpwin = wm->windows.first; tmpwin; tmpwin = tmpwin->next) {
260                 if (tmpwin == win)
261                         continue;
262                 if (tmpwin->screen->temp == 0)
263                         break;
264         }
265
266         if (tmpwin == NULL)
267                 do_exit = 1;
268         
269         if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved) {
270                 if (do_exit) {
271                         if (!GHOST_confirmQuit(win->ghostwin))
272                                 return;
273                 }
274         }
275
276         /* let WM_exit do all freeing, for correct quit.blend save */
277         if (do_exit) {
278                 WM_exit(C);
279         }
280         else {
281                 bScreen *screen = win->screen;
282                 
283                 BLI_remlink(&wm->windows, win);
284                 
285                 wm_draw_window_clear(win);
286                 
287                 CTX_wm_window_set(C, win);  /* needed by handlers */
288                 WM_event_remove_handlers(C, &win->handlers);
289                 WM_event_remove_handlers(C, &win->modalhandlers);
290                 ED_screen_exit(C, win, win->screen); 
291                 
292                 wm_window_free(C, wm, win);
293         
294                 /* if temp screen, delete it after window free (it stops jobs that can access it) */
295                 if (screen->temp) {
296                         Main *bmain = CTX_data_main(C);
297                         BKE_libblock_free(&bmain->screen, screen);
298                 }
299         }               
300 }
301
302 void wm_window_title(wmWindowManager *wm, wmWindow *win)
303 {
304         if (win->screen && win->screen->temp) {
305                 /* nothing to do for 'temp' windows,
306                  * because WM_window_open_temp always sets window title  */
307         }
308         else {
309                 
310                 /* this is set to 1 if you don't have startup.blend open */
311                 if (G.save_over && G.main->name[0]) {
312                         char str[sizeof(G.main->name) + 24];
313                         BLI_snprintf(str, sizeof(str), "Blender%s [%s%s]", wm->file_saved ? "" : "*", G.main->name,
314                                      G.main->recovered ? " (Recovered)" : "");
315                         GHOST_SetTitle(win->ghostwin, str);
316                 }
317                 else
318                         GHOST_SetTitle(win->ghostwin, "Blender");
319
320                 /* Informs GHOST of unsaved changes, to set window modified visual indicator (MAC OS X)
321                  * and to give hint of unsaved changes for a user warning mechanism
322                  * in case of OS application terminate request (e.g. OS Shortcut Alt+F4, Cmd+Q, (...), or session end) */
323                 GHOST_SetWindowModifiedState(win->ghostwin, (GHOST_TUns8) !wm->file_saved);
324                 
325 #if defined(__APPLE__) && !defined(GHOST_COCOA)
326                 if (wm->file_saved)
327                         GHOST_SetWindowState(win->ghostwin, GHOST_kWindowStateUnModified);
328                 else
329                         GHOST_SetWindowState(win->ghostwin, GHOST_kWindowStateModified);
330 #endif
331         }
332 }
333
334 /* belongs to below */
335 static void wm_window_add_ghostwindow(const char *title, wmWindow *win)
336 {
337         GHOST_WindowHandle ghostwin;
338         static int multisamples = -1;
339         int scr_w, scr_h, posy;
340         
341         /* force setting multisamples only once, it requires restart - and you cannot 
342          * mix it, either all windows have it, or none (tested in OSX opengl) */
343         if (multisamples == -1)
344                 multisamples = U.ogl_multisamples;
345         
346         wm_get_screensize(&scr_w, &scr_h);
347         posy = (scr_h - win->posy - win->sizey);
348         
349         ghostwin = GHOST_CreateWindow(g_system, title,
350                                       win->posx, posy, win->sizex, win->sizey,
351                                       (GHOST_TWindowState)win->windowstate,
352                                       GHOST_kDrawingContextTypeOpenGL,
353                                       0 /* no stereo */,
354                                       multisamples /* AA */);
355         
356         if (ghostwin) {
357                 GHOST_RectangleHandle bounds;
358                 
359                 /* needed so we can detect the graphics card below */
360                 GPU_extensions_init();
361                 
362                 /* set the state*/
363                 GHOST_SetWindowState(ghostwin, (GHOST_TWindowState)win->windowstate);
364
365                 win->ghostwin = ghostwin;
366                 GHOST_SetWindowUserData(ghostwin, win); /* pointer back */
367                 
368                 if (win->eventstate == NULL)
369                         win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
370                 
371                 /* until screens get drawn, make it nice gray */
372                 glClearColor(0.55, 0.55, 0.55, 0.0);
373                 /* Crash on OSS ATI: bugs.launchpad.net/ubuntu/+source/mesa/+bug/656100 */
374                 if (!GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE)) {
375                         glClear(GL_COLOR_BUFFER_BIT);
376                 }
377                 
378                 /* displays with larger native pixels, like Macbook. Used to scale dpi with */
379                 /* needed here, because it's used before it reads userdef */
380                 U.pixelsize = GHOST_GetNativePixelSize(win->ghostwin);
381                 BKE_userdef_state();
382                 
383                 /* store actual window size in blender window */
384                 bounds = GHOST_GetClientBounds(win->ghostwin);
385                 win->sizex = GHOST_GetWidthRectangle(bounds);
386                 win->sizey = GHOST_GetHeightRectangle(bounds);
387                 GHOST_DisposeRectangle(bounds);
388
389                 
390                 wm_window_swap_buffers(win);
391                 
392                 //GHOST_SetWindowState(ghostwin, GHOST_kWindowStateModified);
393                 
394                 /* standard state vars for window */
395                 glEnable(GL_SCISSOR_TEST);
396                 GPU_state_init();
397         }
398 }
399
400 /* for wmWindows without ghostwin, open these and clear */
401 /* window size is read from window, if 0 it uses prefsize */
402 /* called in WM_check, also inits stuff after file read */
403 void wm_window_add_ghostwindows(wmWindowManager *wm)
404 {
405         wmKeyMap *keymap;
406         wmWindow *win;
407         
408         /* no commandline prefsize? then we set this.
409          * Note that these values will be used only
410          * when there is no startup.blend yet.
411          */
412         if (wm_init_state.size_x == 0) {
413                 wm_get_screensize(&wm_init_state.size_x, &wm_init_state.size_y);
414                 
415 #if defined(__APPLE__) && !defined(GHOST_COCOA)
416                 /* Cocoa provides functions to get correct max window size */
417                 {
418                         extern void wm_set_apple_prefsize(int, int);    /* wm_apple.c */
419                         
420                         wm_set_apple_prefsize(wm_init_state.size_x, wm_init_state.size_y);
421                 }
422 #else
423                 wm_init_state.start_x = 0;
424                 wm_init_state.start_y = 0;
425                 
426 #endif
427         }
428         
429         for (win = wm->windows.first; win; win = win->next) {
430                 if (win->ghostwin == NULL) {
431                         if ((win->sizex == 0) || (wm_init_state.override_flag & WIN_OVERRIDE_GEOM)) {
432                                 win->posx = wm_init_state.start_x;
433                                 win->posy = wm_init_state.start_y;
434                                 win->sizex = wm_init_state.size_x;
435                                 win->sizey = wm_init_state.size_y;
436
437                                 /* we can't properly resize a maximized window */
438                                 win->windowstate = GHOST_kWindowStateNormal;
439
440                                 wm_init_state.override_flag &= ~WIN_OVERRIDE_GEOM;
441                         }
442
443                         if (wm_init_state.override_flag & WIN_OVERRIDE_WINSTATE) {
444                                 win->windowstate = wm_init_state.windowstate;
445                                 wm_init_state.override_flag &= ~WIN_OVERRIDE_WINSTATE;
446                         }
447
448                         wm_window_add_ghostwindow("Blender", win);
449                 }
450                 /* happens after fileread */
451                 if (win->eventstate == NULL)
452                         win->eventstate = MEM_callocN(sizeof(wmEvent), "window event state");
453
454                 /* add keymap handlers (1 handler for all keys in map!) */
455                 keymap = WM_keymap_find(wm->defaultconf, "Window", 0, 0);
456                 WM_event_add_keymap_handler(&win->handlers, keymap);
457                 
458                 keymap = WM_keymap_find(wm->defaultconf, "Screen", 0, 0);
459                 WM_event_add_keymap_handler(&win->handlers, keymap);
460
461                 keymap = WM_keymap_find(wm->defaultconf, "Screen Editing", 0, 0);
462                 WM_event_add_keymap_handler(&win->modalhandlers, keymap);
463                 
464                 /* add drop boxes */
465                 {
466                         ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
467                         WM_event_add_dropbox_handler(&win->handlers, lb);
468                 }
469                 wm_window_title(wm, win);
470         }
471 }
472
473 /* new window, no screen yet, but we open ghostwindow for it */
474 /* also gets the window level handlers */
475 /* area-rip calls this */
476 wmWindow *WM_window_open(bContext *C, rcti *rect)
477 {
478         wmWindow *win = wm_window_new(C);
479         
480         win->posx = rect->xmin;
481         win->posy = rect->ymin;
482         win->sizex = BLI_rcti_size_x(rect);
483         win->sizey = BLI_rcti_size_y(rect);
484
485         win->drawmethod = U.wmdrawmethod;
486         win->drawdata = NULL;
487         
488         WM_check(C);
489         
490         return win;
491 }
492
493 /* uses screen->temp tag to define what to do, currently it limits
494  * to only one "temp" window for render out, preferences, filewindow, etc */
495 /* type is defined in WM_api.h */
496
497 void WM_window_open_temp(bContext *C, rcti *position, int type)
498 {
499         wmWindow *win;
500         ScrArea *sa;
501         
502         /* changes rect to fit within desktop */
503         wm_window_check_position(position);
504         
505         /* test if we have a temp screen already */
506         for (win = CTX_wm_manager(C)->windows.first; win; win = win->next)
507                 if (win->screen->temp)
508                         break;
509         
510         /* add new window? */
511         if (win == NULL) {
512                 win = wm_window_new(C);
513                 
514                 win->posx = position->xmin;
515                 win->posy = position->ymin;
516         }
517         
518         win->sizex = BLI_rcti_size_x(position);
519         win->sizey = BLI_rcti_size_y(position);
520         
521         if (win->ghostwin) {
522                 wm_window_set_size(win, win->sizex, win->sizey);
523                 wm_window_raise(win);
524         }
525         
526         /* add new screen? */
527         if (win->screen == NULL)
528                 win->screen = ED_screen_add(win, CTX_data_scene(C), "temp");
529         win->screen->temp = 1; 
530         
531         /* make window active, and validate/resize */
532         CTX_wm_window_set(C, win);
533         WM_check(C);
534         
535         /* ensure it shows the right spacetype editor */
536         sa = win->screen->areabase.first;
537         CTX_wm_area_set(C, sa);
538         
539         if (type == WM_WINDOW_RENDER) {
540                 ED_area_newspace(C, sa, SPACE_IMAGE);
541         }
542         else {
543                 ED_area_newspace(C, sa, SPACE_USERPREF);
544         }
545         
546         ED_screen_set(C, win->screen);
547         ED_screen_refresh(CTX_wm_manager(C), win); /* test scale */
548         
549         if (sa->spacetype == SPACE_IMAGE)
550                 GHOST_SetTitle(win->ghostwin, IFACE_("Blender Render"));
551         else if (ELEM(sa->spacetype, SPACE_OUTLINER, SPACE_USERPREF))
552                 GHOST_SetTitle(win->ghostwin, IFACE_("Blender User Preferences"));
553         else if (sa->spacetype == SPACE_FILE)
554                 GHOST_SetTitle(win->ghostwin, IFACE_("Blender File View"));
555         else
556                 GHOST_SetTitle(win->ghostwin, "Blender");
557 }
558
559
560 /* ****************** Operators ****************** */
561
562 /* operator callback */
563 int wm_window_duplicate_exec(bContext *C, wmOperator *UNUSED(op))
564 {
565         wm_window_copy(C, CTX_wm_window(C));
566         WM_check(C);
567         
568         WM_event_add_notifier(C, NC_WINDOW | NA_ADDED, NULL);
569         
570         return OPERATOR_FINISHED;
571 }
572
573
574 /* fullscreen operator callback */
575 int wm_window_fullscreen_toggle_exec(bContext *C, wmOperator *UNUSED(op))
576 {
577         wmWindow *window = CTX_wm_window(C);
578         GHOST_TWindowState state;
579
580         if (G.background)
581                 return OPERATOR_CANCELLED;
582
583         state = GHOST_GetWindowState(window->ghostwin);
584         if (state != GHOST_kWindowStateFullScreen)
585                 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateFullScreen);
586         else
587                 GHOST_SetWindowState(window->ghostwin, GHOST_kWindowStateNormal);
588
589         return OPERATOR_FINISHED;
590         
591 }
592
593
594 /* ************ events *************** */
595
596 static void wm_convert_cursor_position(wmWindow *win, int *x, int *y)
597 {
598         float fac = GHOST_GetNativePixelSize(win->ghostwin);
599         
600         GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
601         *x *= fac;
602         
603         *y = (win->sizey - 1) - *y;
604         *y *= fac;
605 }
606
607
608 void wm_get_cursor_position(wmWindow *win, int *x, int *y)
609 {
610         GHOST_GetCursorPosition(g_system, x, y);
611         wm_convert_cursor_position(win, x, y);
612 }
613
614 typedef enum {
615         SHIFT    = 's',
616         CONTROL  = 'c',
617         ALT      = 'a',
618         OS       = 'C'
619 } modifierKeyType;
620
621 /* check if specified modifier key type is pressed */
622 static int query_qual(modifierKeyType qual) 
623 {
624         GHOST_TModifierKeyMask left, right;
625         int val = 0;
626         
627         switch (qual) {
628                 case SHIFT:
629                         left = GHOST_kModifierKeyLeftShift;
630                         right = GHOST_kModifierKeyRightShift;
631                         break;
632                 case CONTROL:
633                         left = GHOST_kModifierKeyLeftControl;
634                         right = GHOST_kModifierKeyRightControl;
635                         break;
636                 case OS:
637                         left = right = GHOST_kModifierKeyOS;
638                         break;
639                 case ALT:
640                 default:
641                         left = GHOST_kModifierKeyLeftAlt;
642                         right = GHOST_kModifierKeyRightAlt;
643                         break;
644         }
645         
646         GHOST_GetModifierKeyState(g_system, left, &val);
647         if (!val)
648                 GHOST_GetModifierKeyState(g_system, right, &val);
649         
650         return val;
651 }
652
653 void wm_window_make_drawable(bContext *C, wmWindow *win) 
654 {
655         wmWindowManager *wm = CTX_wm_manager(C);
656
657         if (win != wm->windrawable && win->ghostwin) {
658 //              win->lmbut = 0; /* keeps hanging when mousepressed while other window opened */
659                 
660                 wm->windrawable = win;
661                 if (G.debug & G_DEBUG_EVENTS) {
662                         printf("%s: set drawable %d\n", __func__, win->winid);
663                 }
664                 GHOST_ActivateWindowDrawingContext(win->ghostwin);
665                 
666                 /* this can change per window */
667                 U.pixelsize = GHOST_GetNativePixelSize(win->ghostwin);
668                 BKE_userdef_state();
669         }
670 }
671
672 /* called by ghost, here we handle events for windows themselves or send to event system */
673 /* mouse coordinate converversion happens here */
674 static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr)
675 {
676         bContext *C = C_void_ptr;
677         wmWindowManager *wm = CTX_wm_manager(C);
678         GHOST_TEventType type = GHOST_GetEventType(evt);
679         int time = GHOST_GetEventTime(evt);
680         
681         if (type == GHOST_kEventQuit) {
682                 WM_exit(C);
683         }
684         else {
685                 GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt);
686                 GHOST_TEventDataPtr data = GHOST_GetEventData(evt);
687                 wmWindow *win;
688                 
689                 if (!ghostwin) {
690                         /* XXX - should be checked, why are we getting an event here, and */
691                         /* what is it? */
692                         puts("<!> event has no window");
693                         return 1;
694                 }
695                 else if (!GHOST_ValidWindow(g_system, ghostwin)) {
696                         /* XXX - should be checked, why are we getting an event here, and */
697                         /* what is it? */
698                         puts("<!> event has invalid window");
699                         return 1;
700                 }
701                 else {
702                         win = GHOST_GetWindowUserData(ghostwin);
703                 }
704                 
705                 switch (type) {
706                         case GHOST_kEventWindowDeactivate:
707                                 wm_event_add_ghostevent(wm, win, type, time, data);
708                                 win->active = 0; /* XXX */
709                                 
710                                 /* clear modifiers for inactive windows */
711                                 win->eventstate->alt = 0;
712                                 win->eventstate->ctrl = 0;
713                                 win->eventstate->shift = 0;
714                                 win->eventstate->oskey = 0;
715                                 win->eventstate->keymodifier = 0;
716
717                                 break;
718                         case GHOST_kEventWindowActivate: 
719                         {
720                                 GHOST_TEventKeyData kdata;
721                                 wmEvent event;
722                                 int wx, wy;
723                                 
724                                 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
725                                 
726                                 win->active = 1;
727 //                              window_handle(win, INPUTCHANGE, win->active);
728                                 
729                                 /* bad ghost support for modifier keys... so on activate we set the modifiers again */
730                                 kdata.ascii = '\0';
731                                 kdata.utf8_buf[0] = '\0';
732                                 if (win->eventstate->shift && !query_qual(SHIFT)) {
733                                         kdata.key = GHOST_kKeyLeftShift;
734                                         wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
735                                 }
736                                 if (win->eventstate->ctrl && !query_qual(CONTROL)) {
737                                         kdata.key = GHOST_kKeyLeftControl;
738                                         wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
739                                 }
740                                 if (win->eventstate->alt && !query_qual(ALT)) {
741                                         kdata.key = GHOST_kKeyLeftAlt;
742                                         wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
743                                 }
744                                 if (win->eventstate->oskey && !query_qual(OS)) {
745                                         kdata.key = GHOST_kKeyOS;
746                                         wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, time, &kdata);
747                                 }
748                                 /* keymodifier zero, it hangs on hotkeys that open windows otherwise */
749                                 win->eventstate->keymodifier = 0;
750                                 
751                                 /* entering window, update mouse pos. but no event */
752                                 wm_get_cursor_position(win,  &wx, &wy);
753
754                                 win->eventstate->x = wx;
755                                 win->eventstate->y = wy;
756                                 
757                                 win->addmousemove = 1;   /* enables highlighted buttons */
758                                 
759                                 wm_window_make_drawable(C, win);
760
761                                 /* window might be focused by mouse click in configuration of window manager
762                                  * when focus is not following mouse
763                                  * click could have been done on a button and depending on window manager settings
764                                  * click would be passed to blender or not, but in any case button under cursor
765                                  * should be activated, so at max next click on button without moving mouse
766                                  * would trigger it's handle function
767                                  * currently it seems to be common practice to generate new event for, but probably
768                                  * we'll need utility function for this? (sergey)
769                                  */
770                                 event = *(win->eventstate);
771                                 event.type = MOUSEMOVE;
772                                 event.prevx = event.x;
773                                 event.prevy = event.y;
774
775                                 wm_event_add(win, &event);
776
777                                 break;
778                         }
779                         case GHOST_kEventWindowClose:
780                         {
781                                 wm_window_close(C, wm, win);
782                                 break;
783                         }
784                         case GHOST_kEventWindowUpdate:
785                         {
786                                 if (G.debug & G_DEBUG_EVENTS) {
787                                         printf("%s: ghost redraw %d\n", __func__, win->winid);
788                                 }
789                                 
790                                 wm_window_make_drawable(C, win);
791                                 WM_event_add_notifier(C, NC_WINDOW, NULL);
792
793                                 break;
794                         }
795                         case GHOST_kEventWindowSize:
796                         case GHOST_kEventWindowMove:
797                         {
798                                 GHOST_TWindowState state;
799                                 state = GHOST_GetWindowState(win->ghostwin);
800                                 win->windowstate = state;
801
802                                 /* stop screencast if resize */
803                                 if (type == GHOST_kEventWindowSize) {
804                                         WM_jobs_stop(CTX_wm_manager(C), win->screen, NULL);
805                                 }
806                                 
807                                 /* win32: gives undefined window size when minimized */
808                                 if (state != GHOST_kWindowStateMinimized) {
809                                         GHOST_RectangleHandle client_rect;
810                                         int l, t, r, b, scr_w, scr_h;
811                                         int sizex, sizey, posx, posy;
812                                         
813                                         client_rect = GHOST_GetClientBounds(win->ghostwin);
814                                         GHOST_GetRectangle(client_rect, &l, &t, &r, &b);
815                                         
816                                         GHOST_DisposeRectangle(client_rect);
817                                         
818                                         wm_get_screensize(&scr_w, &scr_h);
819                                         sizex = r - l;
820                                         sizey = b - t;
821                                         posx = l;
822                                         posy = scr_h - t - win->sizey;
823
824                                         /*
825                                          * Ghost sometimes send size or move events when the window hasn't changed.
826                                          * One case of this is using compiz on linux. To alleviate the problem
827                                          * we ignore all such event here.
828                                          * 
829                                          * It might be good to eventually do that at Ghost level, but that is for 
830                                          * another time.
831                                          */
832                                         if (win->sizex != sizex ||
833                                             win->sizey != sizey ||
834                                             win->posx != posx ||
835                                             win->posy != posy)
836                                         {
837                                                 win->sizex = sizex;
838                                                 win->sizey = sizey;
839                                                 win->posx = posx;
840                                                 win->posy = posy;
841
842                                                 /* debug prints */
843                                                 if (G.debug & G_DEBUG_EVENTS) {
844                                                         const char *state_str;
845                                                         state = GHOST_GetWindowState(win->ghostwin);
846
847                                                         if (state == GHOST_kWindowStateNormal) {
848                                                                 state_str = "normal";
849                                                         }
850                                                         else if (state == GHOST_kWindowStateMinimized) {
851                                                                 state_str = "minimized";
852                                                         }
853                                                         else if (state == GHOST_kWindowStateMaximized) {
854                                                                 state_str = "maximized";
855                                                         }
856                                                         else if (state == GHOST_kWindowStateFullScreen) {
857                                                                 state_str = "fullscreen";
858                                                         }
859                                                         else {
860                                                                 state_str = "<unknown>";
861                                                         }
862
863                                                         printf("%s: window %d state = %s\n", __func__, win->winid, state_str);
864
865                                                         if (type != GHOST_kEventWindowSize) {
866                                                                 printf("win move event pos %d %d size %d %d\n",
867                                                                        win->posx, win->posy, win->sizex, win->sizey);
868                                                         }
869                                                 }
870                                         
871                                                 wm_window_make_drawable(C, win);
872                                                 wm_draw_window_clear(win);
873                                                 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
874                                                 WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
875                                         }
876                                 }
877                                 break;
878                         }
879                                 
880                         case GHOST_kEventOpenMainFile:
881                         {
882                                 PointerRNA props_ptr;
883                                 wmWindow *oldWindow;
884                                 char *path = GHOST_GetEventData(evt);
885                                 
886                                 if (path) {
887                                         /* operator needs a valid window in context, ensures
888                                          * it is correctly set */
889                                         oldWindow = CTX_wm_window(C);
890                                         CTX_wm_window_set(C, win);
891                                         
892                                         WM_operator_properties_create(&props_ptr, "WM_OT_open_mainfile");
893                                         RNA_string_set(&props_ptr, "filepath", path);
894                                         WM_operator_name_call(C, "WM_OT_open_mainfile", WM_OP_EXEC_DEFAULT, &props_ptr);
895                                         WM_operator_properties_free(&props_ptr);
896                                         
897                                         CTX_wm_window_set(C, oldWindow);
898                                 }
899                                 break;
900                         }
901                         case GHOST_kEventDraggingDropDone:
902                         {
903                                 wmEvent event;
904                                 GHOST_TEventDragnDropData *ddd = GHOST_GetEventData(evt);
905                                 int wx, wy;
906                                 
907                                 /* entering window, update mouse pos */
908                                 wm_get_cursor_position(win, &wx, &wy);
909                                 win->eventstate->x = wx;
910                                 win->eventstate->y = wy;
911                                 
912                                 event = *(win->eventstate);  /* copy last state, like mouse coords */
913                                 
914                                 /* activate region */
915                                 event.type = MOUSEMOVE;
916                                 event.prevx = event.x;
917                                 event.prevy = event.y;
918                                 
919                                 wm->winactive = win; /* no context change! c->wm->windrawable is drawable, or for area queues */
920                                 win->active = 1;
921                                 
922                                 wm_event_add(win, &event);
923                                 
924                                 
925                                 /* make blender drop event with custom data pointing to wm drags */
926                                 event.type = EVT_DROP;
927                                 event.val = KM_RELEASE;
928                                 event.custom = EVT_DATA_LISTBASE;
929                                 event.customdata = &wm->drags;
930                                 event.customdatafree = 1;
931                                 
932                                 wm_event_add(win, &event);
933                                 
934                                 /* printf("Drop detected\n"); */
935                                 
936                                 /* add drag data to wm for paths: */
937                                 
938                                 if (ddd->dataType == GHOST_kDragnDropTypeFilenames) {
939                                         GHOST_TStringArray *stra = ddd->data;
940                                         int a, icon;
941                                         
942                                         for (a = 0; a < stra->count; a++) {
943                                                 printf("drop file %s\n", stra->strings[a]);
944                                                 /* try to get icon type from extension */
945                                                 icon = ED_file_extension_icon((char *)stra->strings[a]);
946                                                 
947                                                 WM_event_start_drag(C, icon, WM_DRAG_PATH, stra->strings[a], 0.0);
948                                                 /* void poin should point to string, it makes a copy */
949                                                 break; /* only one drop element supported now */
950                                         }
951                                 }
952                                 
953                                 break;
954                         }
955                         case GHOST_kEventNativeResolutionChange:
956                                 // printf("change, pixel size %f\n", GHOST_GetNativePixelSize(win->ghostwin));
957                                 
958                                 U.pixelsize = GHOST_GetNativePixelSize(win->ghostwin);
959                                 BKE_userdef_state();
960                                 WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
961                                 WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
962
963                                 break;
964                         case GHOST_kEventTrackpad:
965                         {
966                                 GHOST_TEventTrackpadData *pd = data;
967                                 
968                                 wm_convert_cursor_position(win, &pd->x, &pd->y);
969                                 wm_event_add_ghostevent(wm, win, type, time, data);
970                                 break;
971                         }
972                         case GHOST_kEventCursorMove:
973                         {
974                                 GHOST_TEventCursorData *cd = data;
975                                 
976                                 wm_convert_cursor_position(win, &cd->x, &cd->y);
977                                 wm_event_add_ghostevent(wm, win, type, time, data);
978                                 break;
979                         }
980                         default:
981                                 wm_event_add_ghostevent(wm, win, type, time, data);
982                                 break;
983                 }
984
985         }
986         return 1;
987 }
988
989
990 /* This timer system only gives maximum 1 timer event per redraw cycle,
991  * to prevent queues to get overloaded.
992  * Timer handlers should check for delta to decide if they just
993  * update, or follow real time.
994  * Timer handlers can also set duration to match frames passed
995  */
996 static int wm_window_timer(const bContext *C)
997 {
998         wmWindowManager *wm = CTX_wm_manager(C);
999         wmTimer *wt, *wtnext;
1000         wmWindow *win;
1001         double time = PIL_check_seconds_timer();
1002         int retval = 0;
1003         
1004         for (wt = wm->timers.first; wt; wt = wtnext) {
1005                 wtnext = wt->next; /* in case timer gets removed */
1006                 win = wt->win;
1007
1008                 if (wt->sleep == 0) {
1009                         if (time > wt->ntime) {
1010                                 wt->delta = time - wt->ltime;
1011                                 wt->duration += wt->delta;
1012                                 wt->ltime = time;
1013                                 wt->ntime = wt->stime + wt->timestep * ceil(wt->duration / wt->timestep);
1014
1015                                 if (wt->event_type == TIMERJOBS)
1016                                         wm_jobs_timer(C, wm, wt);
1017                                 else if (wt->event_type == TIMERAUTOSAVE)
1018                                         wm_autosave_timer(C, wm, wt);
1019                                 else if (win) {
1020                                         wmEvent event = *(win->eventstate);
1021                                         
1022                                         event.type = wt->event_type;
1023                                         event.val = 0;
1024                                         event.keymodifier = 0;
1025                                         event.custom = EVT_DATA_TIMER;
1026                                         event.customdata = wt;
1027                                         wm_event_add(win, &event);
1028
1029                                         retval = 1;
1030                                 }
1031                         }
1032                 }
1033         }
1034         return retval;
1035 }
1036
1037 void wm_window_process_events(const bContext *C) 
1038 {
1039         int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1040         
1041         if (hasevent)
1042                 GHOST_DispatchEvents(g_system);
1043         
1044         hasevent |= wm_window_timer(C);
1045
1046         /* no event, we sleep 5 milliseconds */
1047         if (hasevent == 0)
1048                 PIL_sleep_ms(5);
1049 }
1050
1051 void wm_window_process_events_nosleep(void) 
1052 {
1053         if (GHOST_ProcessEvents(g_system, 0))
1054                 GHOST_DispatchEvents(g_system);
1055 }
1056
1057 /* exported as handle callback to bke blender.c */
1058 void wm_window_testbreak(void)
1059 {
1060         static double ltime = 0;
1061         double curtime = PIL_check_seconds_timer();
1062         
1063         /* only check for breaks every 50 milliseconds
1064          * if we get called more often.
1065          */
1066         if ((curtime - ltime) > 0.05) {
1067                 int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */
1068                 
1069                 if (hasevent)
1070                         GHOST_DispatchEvents(g_system);
1071                 
1072                 ltime = curtime;
1073         }
1074 }
1075
1076 /* **************** init ********************** */
1077
1078 void wm_ghost_init(bContext *C)
1079 {
1080         if (!g_system) {
1081                 GHOST_EventConsumerHandle consumer = GHOST_CreateEventConsumer(ghost_event_proc, C);
1082                 
1083                 g_system = GHOST_CreateSystem();
1084                 GHOST_AddEventConsumer(g_system, consumer);
1085                 
1086                 if (wm_init_state.native_pixels) {
1087                         GHOST_UseNativePixels();
1088                 }
1089         }
1090 }
1091
1092 void wm_ghost_exit(void)
1093 {
1094         if (g_system)
1095                 GHOST_DisposeSystem(g_system);
1096
1097         g_system = NULL;
1098 }
1099
1100 /* **************** timer ********************** */
1101
1102 /* to (de)activate running timers temporary */
1103 void WM_event_timer_sleep(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer, int dosleep)
1104 {
1105         wmTimer *wt;
1106         
1107         for (wt = wm->timers.first; wt; wt = wt->next)
1108                 if (wt == timer)
1109                         break;
1110
1111         if (wt)
1112                 wt->sleep = dosleep;
1113 }
1114
1115 wmTimer *WM_event_add_timer(wmWindowManager *wm, wmWindow *win, int event_type, double timestep)
1116 {
1117         wmTimer *wt = MEM_callocN(sizeof(wmTimer), "window timer");
1118         
1119         wt->event_type = event_type;
1120         wt->ltime = PIL_check_seconds_timer();
1121         wt->ntime = wt->ltime + timestep;
1122         wt->stime = wt->ltime;
1123         wt->timestep = timestep;
1124         wt->win = win;
1125         
1126         BLI_addtail(&wm->timers, wt);
1127         
1128         return wt;
1129 }
1130
1131 void WM_event_remove_timer(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer *timer)
1132 {
1133         wmTimer *wt;
1134         
1135         /* extra security check */
1136         for (wt = wm->timers.first; wt; wt = wt->next)
1137                 if (wt == timer)
1138                         break;
1139         if (wt) {
1140                 wmWindow *win;
1141                 
1142                 if (wm->reports.reporttimer == wt)
1143                         wm->reports.reporttimer = NULL;
1144                 
1145                 BLI_remlink(&wm->timers, wt);
1146                 if (wt->customdata)
1147                         MEM_freeN(wt->customdata);
1148                 MEM_freeN(wt);
1149                 
1150                 /* there might be events in queue with this timer as customdata */
1151                 for (win = wm->windows.first; win; win = win->next) {
1152                         wmEvent *event;
1153                         for (event = win->queue.first; event; event = event->next) {
1154                                 if (event->customdata == wt) {
1155                                         event->customdata = NULL;
1156                                         event->type = EVENT_NONE;       /* timer users customdata, dont want NULL == NULL */
1157                                 }
1158                         }
1159                 }
1160         }
1161 }
1162
1163 /* ******************* clipboard **************** */
1164
1165 char *WM_clipboard_text_get(int selection)
1166 {
1167         char *p, *p2, *buf, *newbuf;
1168
1169         if (G.background)
1170                 return NULL;
1171
1172         buf = (char *)GHOST_getClipboard(selection);
1173         if (!buf)
1174                 return NULL;
1175         
1176         /* always convert from \r\n to \n */
1177         newbuf = MEM_callocN(strlen(buf) + 1, __func__);
1178
1179         for (p = buf, p2 = newbuf; *p; p++) {
1180                 if (*p != '\r')
1181                         *(p2++) = *p;
1182         }
1183         *p2 = '\0';
1184
1185         free(buf); /* ghost uses regular malloc */
1186         
1187         return newbuf;
1188 }
1189
1190 void WM_clipboard_text_set(char *buf, int selection)
1191 {
1192         if (!G.background) {
1193 #ifdef _WIN32
1194                 /* do conversion from \n to \r\n on Windows */
1195                 char *p, *p2, *newbuf;
1196                 int newlen = 0;
1197                 
1198                 for (p = buf; *p; p++) {
1199                         if (*p == '\n')
1200                                 newlen += 2;
1201                         else
1202                                 newlen++;
1203                 }
1204                 
1205                 newbuf = MEM_callocN(newlen + 1, "WM_clipboard_text_set");
1206         
1207                 for (p = buf, p2 = newbuf; *p; p++, p2++) {
1208                         if (*p == '\n') {
1209                                 *(p2++) = '\r'; *p2 = '\n';
1210                         }
1211                         else *p2 = *p;
1212                 }
1213                 *p2 = '\0';
1214         
1215                 GHOST_putClipboard((GHOST_TInt8 *)newbuf, selection);
1216                 MEM_freeN(newbuf);
1217 #else
1218                 GHOST_putClipboard((GHOST_TInt8 *)buf, selection);
1219 #endif
1220         }
1221 }
1222
1223 /* ******************* progress bar **************** */
1224
1225 void WM_progress_set(wmWindow *win, float progress)
1226 {
1227         GHOST_SetProgressBar(win->ghostwin, progress);
1228 }
1229
1230 void WM_progress_clear(wmWindow *win)
1231 {
1232         GHOST_EndProgressBar(win->ghostwin);
1233 }
1234
1235 /* ************************************ */
1236
1237 void wm_window_get_position(wmWindow *win, int *posx_r, int *posy_r) 
1238 {
1239         *posx_r = win->posx;
1240         *posy_r = win->posy;
1241 }
1242
1243 void wm_window_set_size(wmWindow *win, int width, int height) 
1244 {
1245         GHOST_SetClientSize(win->ghostwin, width, height);
1246 }
1247
1248 void wm_window_lower(wmWindow *win) 
1249 {
1250         GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderBottom);
1251 }
1252
1253 void wm_window_raise(wmWindow *win) 
1254 {
1255         GHOST_SetWindowOrder(win->ghostwin, GHOST_kWindowOrderTop);
1256 }
1257
1258 void wm_window_swap_buffers(wmWindow *win)
1259 {
1260         
1261 #ifdef WIN32
1262         glDisable(GL_SCISSOR_TEST);
1263         GHOST_SwapWindowBuffers(win->ghostwin);
1264         glEnable(GL_SCISSOR_TEST);
1265 #else
1266         GHOST_SwapWindowBuffers(win->ghostwin);
1267 #endif
1268 }
1269
1270
1271 /* ******************* exported api ***************** */
1272
1273
1274 /* called whem no ghost system was initialized */
1275 void WM_init_state_size_set(int stax, int stay, int sizx, int sizy)
1276 {
1277         wm_init_state.start_x = stax; /* left hand pos */
1278         wm_init_state.start_y = stay; /* bottom pos */
1279         wm_init_state.size_x = sizx < 640 ? 640 : sizx;
1280         wm_init_state.size_y = sizy < 480 ? 480 : sizy;
1281         wm_init_state.override_flag |= WIN_OVERRIDE_GEOM;
1282 }
1283
1284 /* for borderless and border windows set from command-line */
1285 void WM_init_state_fullscreen_set(void)
1286 {
1287         wm_init_state.windowstate = GHOST_kWindowStateFullScreen;
1288         wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1289 }
1290
1291 void WM_init_state_normal_set(void)
1292 {
1293         wm_init_state.windowstate = GHOST_kWindowStateNormal;
1294         wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE;
1295 }
1296
1297 void WM_init_native_pixels(int do_it)
1298 {
1299         wm_init_state.native_pixels = do_it;
1300 }
1301
1302 /* This function requires access to the GHOST_SystemHandle (g_system) */
1303 void WM_cursor_warp(wmWindow *win, int x, int y)
1304 {
1305         if (win && win->ghostwin) {
1306                 float f = GHOST_GetNativePixelSize(win->ghostwin);
1307                 int oldx = x, oldy = y;
1308
1309                 x = x / f;
1310                 y = y / f;
1311                 y = win->sizey - y - 1;
1312
1313                 GHOST_ClientToScreen(win->ghostwin, x, y, &x, &y);
1314                 GHOST_SetCursorPosition(g_system, x, y);
1315
1316                 win->eventstate->prevx = oldx;
1317                 win->eventstate->prevy = oldy;
1318         }
1319 }
1320
1321 /* support for native pixel size */
1322 /* mac retina opens window in size X, but it has up to 2 x more pixels */
1323 int WM_window_pixels_x(wmWindow *win)
1324 {
1325         float f = GHOST_GetNativePixelSize(win->ghostwin);
1326         
1327         return (int)(f * (float)win->sizex);
1328 }
1329
1330 int WM_window_pixels_y(wmWindow *win)
1331 {
1332         float f = GHOST_GetNativePixelSize(win->ghostwin);
1333         
1334         return (int)(f * (float)win->sizey);
1335         
1336 }