diff options
Diffstat (limited to 'source/blender/windowmanager/intern')
18 files changed, 2445 insertions, 1302 deletions
diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index b76a1f1d422..8be7555b34a 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -61,6 +61,7 @@ #include "wm.h" #include "ED_screen.h" +#include "BKE_undo_system.h" #ifdef WITH_PYTHON #include "BPY_extern.h" @@ -107,6 +108,17 @@ void WM_operator_free(wmOperator *op) MEM_freeN(op); } +void WM_operator_free_all_after(wmWindowManager *wm, struct wmOperator *op) +{ + op = op->next; + while (op != NULL) { + wmOperator *op_next = op->next; + BLI_remlink(&wm->operators, op); + WM_operator_free(op); + op = op_next; + } +} + /** * Use with extreme care!, * properties, customdata etc - must be compatible. @@ -149,18 +161,23 @@ static void wm_reports_free(wmWindowManager *wm) void wm_operator_register(bContext *C, wmOperator *op) { wmWindowManager *wm = CTX_wm_manager(C); - int tot; + int tot = 0; BLI_addtail(&wm->operators, op); - tot = BLI_listbase_count(&wm->operators); - - while (tot > MAX_OP_REGISTERED) { - wmOperator *opt = wm->operators.first; - BLI_remlink(&wm->operators, opt); - WM_operator_free(opt); - tot--; + + /* only count registered operators */ + while (op) { + wmOperator *op_prev = op->prev; + if (op->type->flag & OPTYPE_REGISTER) { + tot += 1; + } + if (tot > MAX_OP_REGISTERED) { + BLI_remlink(&wm->operators, op); + WM_operator_free(op); + } + op = op_prev; } - + /* so the console is redrawn */ WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL); WM_event_add_notifier(C, NC_WM | ND_HISTORY, NULL); @@ -321,6 +338,14 @@ void WM_menutype_free(void) menutypes_hash = NULL; } +bool WM_menutype_poll(bContext *C, MenuType *mt) +{ + if (mt->poll != NULL) { + return mt->poll(C, mt); + } + return true; +} + /* ****************************************** */ void WM_keymap_init(bContext *C) @@ -414,7 +439,7 @@ void wm_clear_default_size(bContext *C) /* on startup, it adds all data, for matching */ void wm_add_default(bContext *C) { - wmWindowManager *wm = BKE_libblock_alloc(CTX_data_main(C), ID_WM, "WinMan"); + wmWindowManager *wm = BKE_libblock_alloc(CTX_data_main(C), ID_WM, "WinMan", 0); wmWindow *win; bScreen *screen = CTX_wm_screen(C); /* XXX from file read hrmf */ @@ -461,25 +486,33 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm) WM_drag_free_list(&wm->drags); wm_reports_free(wm); - + + if (wm->undo_stack) { + BKE_undosys_stack_destroy(wm->undo_stack); + wm->undo_stack = NULL; + } + if (C && CTX_wm_manager(C) == wm) CTX_wm_manager_set(C, NULL); } void wm_close_and_free_all(bContext *C, ListBase *wmlist) { - Main *bmain = CTX_data_main(C); wmWindowManager *wm; - + while ((wm = wmlist->first)) { wm_close_and_free(C, wm); BLI_remlink(wmlist, wm); - BKE_libblock_free_data(bmain, &wm->id); + BKE_libblock_free_data(&wm->id, true); MEM_freeN(wm); } } void WM_main(bContext *C) { + /* Single refresh before handling events. + * This ensures we don't run operators before the depsgraph has been evaluated. */ + wm_event_do_refresh_wm_and_depsgraph(C); + while (1) { /* get events from ghost, handle window events, add to window queues */ diff --git a/source/blender/windowmanager/intern/wm_cursors.c b/source/blender/windowmanager/intern/wm_cursors.c index d4c3928bd6c..ad5e83ceda7 100644 --- a/source/blender/windowmanager/intern/wm_cursors.c +++ b/source/blender/windowmanager/intern/wm_cursors.c @@ -213,13 +213,10 @@ void WM_cursor_grab_enable(wmWindow *win, bool wrap, bool hide, int bounds[4]) } if ((G.debug & G_DEBUG) == 0) { if (win->ghostwin) { - const GHOST_TabletData *tabletdata = GHOST_GetTabletData(win->ghostwin); - /* Note: There is no tabletdata on Windows if no tablet device is connected. */ - if (!tabletdata) - GHOST_SetCursorGrab(win->ghostwin, mode, bounds, NULL); - else if (tabletdata->Active == GHOST_kTabletModeNone) + if (win->eventstate->is_motion_absolute == false) { GHOST_SetCursorGrab(win->ghostwin, mode, bounds, NULL); + } win->grabcursor = mode; } diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c index 3a53906a8e8..dd01efdb4c4 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.c +++ b/source/blender/windowmanager/intern/wm_dragdrop.c @@ -185,7 +185,7 @@ void WM_drag_free_list(struct ListBase *lb) } } -static const char *dropbox_active(bContext *C, ListBase *handlers, wmDrag *drag, wmEvent *event) +static const char *dropbox_active(bContext *C, ListBase *handlers, wmDrag *drag, const wmEvent *event) { wmEventHandler *handler = handlers->first; for (; handler; handler = handler->next) { @@ -203,7 +203,7 @@ static const char *dropbox_active(bContext *C, ListBase *handlers, wmDrag *drag, } /* return active operator name when mouse is in box */ -static const char *wm_dropbox_active(bContext *C, wmDrag *drag, wmEvent *event) +static const char *wm_dropbox_active(bContext *C, wmDrag *drag, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); ScrArea *sa = CTX_wm_area(C); @@ -223,7 +223,7 @@ static const char *wm_dropbox_active(bContext *C, wmDrag *drag, wmEvent *event) } -static void wm_drop_operator_options(bContext *C, wmDrag *drag, wmEvent *event) +static void wm_drop_operator_options(bContext *C, wmDrag *drag, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); const int winsize_x = WM_window_pixels_x(win); @@ -253,7 +253,7 @@ static void wm_drop_operator_options(bContext *C, wmDrag *drag, wmEvent *event) } /* called in inner handler loop, region context */ -void wm_drags_check_ops(bContext *C, wmEvent *event) +void wm_drags_check_ops(bContext *C, const wmEvent *event) { wmWindowManager *wm = CTX_wm_manager(C); wmDrag *drag; diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c index 77ffa46b990..e1f21699057 100644 --- a/source/blender/windowmanager/intern/wm_draw.c +++ b/source/blender/windowmanager/intern/wm_draw.c @@ -128,10 +128,10 @@ static bool wm_area_test_invalid_backbuf(ScrArea *sa) if (sa->spacetype == SPACE_VIEW3D) return (((View3D *)sa->spacedata.first)->flag & V3D_INVALID_BACKBUF) != 0; else - return 1; + return true; } -static void wm_region_test_render_do_draw(bScreen *screen, ScrArea *sa, ARegion *ar) +static void wm_region_test_render_do_draw(const bScreen *screen, ScrArea *sa, ARegion *ar) { /* tag region for redraw from render engine preview running inside of it */ if (sa->spacetype == SPACE_VIEW3D) { @@ -157,6 +157,46 @@ static void wm_region_test_render_do_draw(bScreen *screen, ScrArea *sa, ARegion /********************** draw all **************************/ /* - reference method, draw all each time */ +typedef struct WindowDrawCB { + struct WindowDrawCB *next, *prev; + + void(*draw)(const struct wmWindow *, void *); + void *customdata; + +} WindowDrawCB; + +void *WM_draw_cb_activate( + wmWindow *win, + void(*draw)(const struct wmWindow *, void *), + void *customdata) +{ + WindowDrawCB *wdc = MEM_callocN(sizeof(*wdc), "WindowDrawCB"); + + BLI_addtail(&win->drawcalls, wdc); + wdc->draw = draw; + wdc->customdata = customdata; + + return wdc; +} + +void WM_draw_cb_exit(wmWindow *win, void *handle) +{ + for (WindowDrawCB *wdc = win->drawcalls.first; wdc; wdc = wdc->next) { + if (wdc == (WindowDrawCB *)handle) { + BLI_remlink(&win->drawcalls, wdc); + MEM_freeN(wdc); + return; + } + } +} + +static void wm_draw_callbacks(wmWindow *win) +{ + for (WindowDrawCB *wdc = win->drawcalls.first; wdc; wdc = wdc->next) { + wdc->draw(win, wdc->customdata); + } +} + static void wm_method_draw_full(bContext *C, wmWindow *win) { bScreen *screen = win->screen; @@ -181,8 +221,9 @@ static void wm_method_draw_full(bContext *C, wmWindow *win) CTX_wm_area_set(C, NULL); } - ED_screen_draw(win); - win->screen->do_draw = false; + ED_screen_draw_edges(win); + screen->do_draw = false; + wm_draw_callbacks(win); /* draw overlapping regions */ for (ar = screen->regionbase.first; ar; ar = ar->next) { @@ -318,17 +359,19 @@ static void wm_method_draw_overlap_all(bContext *C, wmWindow *win, int exchange) /* after area regions so we can do area 'overlay' drawing */ if (screen->do_draw) { - ED_screen_draw(win); - win->screen->do_draw = false; + ED_screen_draw_edges(win); + screen->do_draw = false; + wm_draw_callbacks(win); if (exchange) screen->swap = WIN_FRONT_OK; } else if (exchange) { if (screen->swap == WIN_FRONT_OK) { - ED_screen_draw(win); - win->screen->do_draw = false; + ED_screen_draw_edges(win); + screen->do_draw = false; screen->swap = WIN_BOTH_OK; + wm_draw_callbacks(win); } else if (screen->swap == WIN_BACK_OK) screen->swap = WIN_FRONT_OK; @@ -514,7 +557,6 @@ static void wm_draw_region_blend(wmWindow *win, ARegion *ar, wmDrawTriple *tripl static void wm_method_draw_triple(bContext *C, wmWindow *win) { wmWindowManager *wm = CTX_wm_manager(C); - wmDrawTriple *triple; wmDrawData *dd, *dd_next, *drawdata = win->drawdata.first; bScreen *screen = win->screen; ScrArea *sa; @@ -554,7 +596,7 @@ static void wm_method_draw_triple(bContext *C, wmWindow *win) MEM_freeN(dd); } - triple = drawdata->triple; + wmDrawTriple *triple = drawdata->triple; /* draw marked area regions */ for (sa = screen->areabase.first; sa; sa = sa->next) { @@ -562,7 +604,6 @@ static void wm_method_draw_triple(bContext *C, wmWindow *win) for (ar = sa->regionbase.first; ar; ar = ar->next) { if (ar->swinid && ar->do_draw) { - if (ar->overlap == false) { CTX_wm_region_set(C, ar); ED_region_do_draw(C, ar); @@ -622,8 +663,9 @@ static void wm_method_draw_triple(bContext *C, wmWindow *win) } /* after area regions so we can do area 'overlay' drawing */ - ED_screen_draw(win); + ED_screen_draw_edges(win); win->screen->do_draw = false; + wm_draw_callbacks(win); /* draw floating regions (menus) */ for (ar = screen->regionbase.first; ar; ar = ar->next) { @@ -645,7 +687,7 @@ static void wm_method_draw_triple(bContext *C, wmWindow *win) } } -static void wm_method_draw_triple_multiview(bContext *C, wmWindow *win, StereoViews sview) +static void wm_method_draw_triple_multiview(bContext *C, wmWindow *win, eStereoViews sview) { wmWindowManager *wm = CTX_wm_manager(C); wmDrawData *drawdata; @@ -792,9 +834,10 @@ static void wm_method_draw_triple_multiview(bContext *C, wmWindow *win, StereoVi } /* after area regions so we can do area 'overlay' drawing */ - ED_screen_draw(win); + ED_screen_draw_edges(win); if (sview == STEREO_RIGHT_ID) win->screen->do_draw = false; + wm_draw_callbacks(win); /* draw floating regions (menus) */ for (ar = screen->regionbase.first; ar; ar = ar->next) { @@ -826,11 +869,12 @@ static void wm_method_draw_triple_multiview(bContext *C, wmWindow *win, StereoVi /* quick test to prevent changing window drawable */ static bool wm_draw_update_test_window(wmWindow *win) { + const bScreen *screen = win->screen; ScrArea *sa; ARegion *ar; bool do_draw = false; - for (ar = win->screen->regionbase.first; ar; ar = ar->next) { + for (ar = screen->regionbase.first; ar; ar = ar->next) { if (ar->do_draw_overlay) { wm_tag_redraw_overlay(win, ar); ar->do_draw_overlay = false; @@ -839,9 +883,9 @@ static bool wm_draw_update_test_window(wmWindow *win) do_draw = true; } - for (sa = win->screen->areabase.first; sa; sa = sa->next) { + for (sa = screen->areabase.first; sa; sa = sa->next) { for (ar = sa->regionbase.first; ar; ar = ar->next) { - wm_region_test_render_do_draw(win->screen, sa, ar); + wm_region_test_render_do_draw(screen, sa, ar); if (ar->swinid && ar->do_draw) do_draw = true; @@ -849,43 +893,31 @@ static bool wm_draw_update_test_window(wmWindow *win) } if (do_draw) - return 1; + return true; - if (win->screen->do_refresh) - return 1; - if (win->screen->do_draw) - return 1; - if (win->screen->do_draw_gesture) - return 1; - if (win->screen->do_draw_paintcursor) - return 1; - if (win->screen->do_draw_drag) - return 1; + if (screen->do_refresh) + return true; + if (screen->do_draw) + return true; + if (screen->do_draw_gesture) + return true; + if (screen->do_draw_paintcursor) + return true; + if (screen->do_draw_drag) + return true; - return 0; + return false; } static int wm_automatic_draw_method(wmWindow *win) { - /* Ideally all cards would work well with triple buffer, since if it works - * well gives the least redraws and is considerably faster at partial redraw - * for sculpting or drawing overlapping menus. For typically lower end cards - * copy to texture is slow though and so we use overlap instead there. */ - + /* We assume all supported GPUs now support triple buffer well. */ if (win->drawmethod == USER_DRAW_AUTOMATIC) { - /* Windows software driver darkens color on each redraw */ - if (GPU_type_matches(GPU_DEVICE_SOFTWARE, GPU_OS_WIN, GPU_DRIVER_SOFTWARE)) - return USER_DRAW_OVERLAP_FLIP; - else if (GPU_type_matches(GPU_DEVICE_SOFTWARE, GPU_OS_UNIX, GPU_DRIVER_SOFTWARE)) - return USER_DRAW_OVERLAP; - /* drawing lower color depth again degrades colors each time */ - else if (GPU_color_depth() < 24) - return USER_DRAW_OVERLAP; - else - return USER_DRAW_TRIPLE; + return USER_DRAW_TRIPLE; } - else + else { return win->drawmethod; + } } bool WM_is_draw_triple(wmWindow *win) @@ -916,7 +948,6 @@ void wm_draw_update(bContext *C) { wmWindowManager *wm = CTX_wm_manager(C); wmWindow *win; - int drawmethod; #ifdef WITH_OPENSUBDIV BKE_subsurf_free_unused_buffers(); @@ -926,15 +957,14 @@ void wm_draw_update(bContext *C) for (win = wm->windows.first; win; win = win->next) { #ifdef WIN32 - if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_ANY, GPU_DRIVER_ANY)) { - GHOST_TWindowState state = GHOST_GetWindowState(win->ghostwin); - - if (state == GHOST_kWindowStateMinimized) { - /* do not update minimized windows, it gives issues on intel drivers (see [#33223]) - * anyway, it seems logical to skip update for invisible windows - */ - continue; - } + GHOST_TWindowState state = GHOST_GetWindowState(win->ghostwin); + + if (state == GHOST_kWindowStateMinimized) { + /* do not update minimized windows, gives issues on Intel (see T33223) + * and AMD (see T50856). it seems logical to skip update for invisible + * window anyway. + */ + continue; } #endif if (win->drawmethod != U.wmdrawmethod) { @@ -943,16 +973,18 @@ void wm_draw_update(bContext *C) } if (wm_draw_update_test_window(win)) { + bScreen *screen = win->screen; + CTX_wm_window_set(C, win); /* sets context window+screen */ wm_window_make_drawable(wm, win); /* notifiers for screen redraw */ - if (win->screen->do_refresh) + if (screen->do_refresh) ED_screen_refresh(wm, win); - drawmethod = wm_automatic_draw_method(win); + int drawmethod = wm_automatic_draw_method(win); if (win->drawfail) wm_method_draw_overlap_all(C, win, 0); @@ -973,9 +1005,9 @@ void wm_draw_update(bContext *C) } } - win->screen->do_draw_gesture = false; - win->screen->do_draw_paintcursor = false; - win->screen->do_draw_drag = false; + screen->do_draw_gesture = false; + screen->do_draw_paintcursor = false; + screen->do_draw_drag = false; wm_window_swap_buffers(win); diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 9571dfd75d6..bc4dc5217cb 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -43,6 +43,8 @@ #include "MEM_guardedalloc.h" +#include "CLG_log.h" + #include "GHOST_C-api.h" #include "BLI_blenlib.h" @@ -65,6 +67,7 @@ #include "ED_screen.h" #include "ED_view3d.h" #include "ED_util.h" +#include "ED_undo.h" #include "RNA_access.h" @@ -83,6 +86,9 @@ #include "RNA_enum_types.h" +/* Motion in pixels allowed before we don't consider single/double click. */ +#define WM_EVENT_CLICK_WIGGLE_ROOM 2 + static void wm_notifier_clear(wmNotifier *note); static void update_tablet_data(wmWindow *win, wmEvent *event); @@ -91,7 +97,7 @@ static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, PointerRNA /* ************ event management ************** */ -void wm_event_add_ex(wmWindow *win, const wmEvent *event_to_add, const wmEvent *event_to_add_after) +wmEvent *wm_event_add_ex(wmWindow *win, const wmEvent *event_to_add, const wmEvent *event_to_add_after) { wmEvent *event = MEM_mallocN(sizeof(wmEvent), "wmEvent"); @@ -99,6 +105,13 @@ void wm_event_add_ex(wmWindow *win, const wmEvent *event_to_add, const wmEvent * update_tablet_data(win, event); + if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + /* We could have a preference to support relative tablet motion (we can't detect that). */ + event->is_motion_absolute = ( + (event->tablet_data != NULL) && + (event->tablet_data->Active != GHOST_kTabletModeNone)); + } + if (event_to_add_after == NULL) { BLI_addtail(&win->queue, event); } @@ -106,11 +119,12 @@ void wm_event_add_ex(wmWindow *win, const wmEvent *event_to_add, const wmEvent * /* note, strictly speaking this breaks const-correctness, however we're only changing 'next' member */ BLI_insertlinkafter(&win->queue, (void *)event_to_add_after, event); } + return event; } -void wm_event_add(wmWindow *win, const wmEvent *event_to_add) +wmEvent *wm_event_add(wmWindow *win, const wmEvent *event_to_add) { - wm_event_add_ex(win, event_to_add, NULL); + return wm_event_add_ex(win, event_to_add, NULL); } void wm_event_free(wmEvent *event) @@ -263,13 +277,56 @@ static void wm_notifier_clear(wmNotifier *note) memset(((char *)note) + sizeof(Link), 0, sizeof(*note) - sizeof(Link)); } +/** + * Was part of #wm_event_do_notifiers, split out so it can be called once before entering the #WM_main loop. + * This ensures operators don't run before the UI and depsgraph are initialized. + */ +void wm_event_do_refresh_wm_and_depsgraph(bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + uint64_t win_combine_v3d_datamask = 0; + + /* combine datamasks so 1 win doesn't disable UV's in another [#26448] */ + for (wmWindow *win = wm->windows.first; win; win = win->next) { + win_combine_v3d_datamask |= ED_view3d_screen_datamask(win->screen); + } + + /* cached: editor refresh callbacks now, they get context */ + for (wmWindow *win = wm->windows.first; win; win = win->next) { + ScrArea *sa; + + CTX_wm_window_set(C, win); + for (sa = win->screen->areabase.first; sa; sa = sa->next) { + if (sa->do_refresh) { + CTX_wm_area_set(C, sa); + ED_area_do_refresh(C, sa); + } + } + + /* XXX make lock in future, or separated derivedmesh users in scene */ + if (G.is_rendering == false) { + /* depsgraph & animation: update tagged datablocks */ + Main *bmain = CTX_data_main(C); + + /* copied to set's in scene_update_tagged_recursive() */ + win->screen->scene->customdata_mask = win_combine_v3d_datamask; + + /* XXX, hack so operators can enforce datamasks [#26482], gl render */ + win->screen->scene->customdata_mask |= win->screen->scene->customdata_mask_modal; + + BKE_scene_update_tagged(bmain->eval_ctx, bmain, win->screen->scene); + } + } + + CTX_wm_window_set(C, NULL); +} + /* called in mainloop */ void wm_event_do_notifiers(bContext *C) { wmWindowManager *wm = CTX_wm_manager(C); wmNotifier *note, *next; wmWindow *win; - uint64_t win_combine_v3d_datamask = 0; if (wm == NULL) return; @@ -299,13 +356,11 @@ void wm_event_do_notifiers(bContext *C) ED_screen_set(C, note->reference); // XXX hrms, think this over! - if (G.debug & G_DEBUG_EVENTS) - printf("%s: screen set %p\n", __func__, note->reference); + CLOG_INFO(WM_LOG_EVENTS, 1, "screen set %p", note->reference); } else if (note->data == ND_SCREENDELETE) { ED_screen_delete(C, note->reference); // XXX hrms, think this over! - if (G.debug & G_DEBUG_EVENTS) - printf("%s: screen delete %p\n", __func__, note->reference); + CLOG_INFO(WM_LOG_EVENTS, 1, "screen delete %p", note->reference); } } } @@ -373,39 +428,7 @@ void wm_event_do_notifiers(bContext *C) MEM_freeN(note); } - /* combine datamasks so 1 win doesn't disable UV's in another [#26448] */ - for (win = wm->windows.first; win; win = win->next) { - win_combine_v3d_datamask |= ED_view3d_screen_datamask(win->screen); - } - - /* cached: editor refresh callbacks now, they get context */ - for (win = wm->windows.first; win; win = win->next) { - ScrArea *sa; - - CTX_wm_window_set(C, win); - for (sa = win->screen->areabase.first; sa; sa = sa->next) { - if (sa->do_refresh) { - CTX_wm_area_set(C, sa); - ED_area_do_refresh(C, sa); - } - } - - /* XXX make lock in future, or separated derivedmesh users in scene */ - if (G.is_rendering == false) { - /* depsgraph & animation: update tagged datablocks */ - Main *bmain = CTX_data_main(C); - - /* copied to set's in scene_update_tagged_recursive() */ - win->screen->scene->customdata_mask = win_combine_v3d_datamask; - - /* XXX, hack so operators can enforce datamasks [#26482], gl render */ - win->screen->scene->customdata_mask |= win->screen->scene->customdata_mask_modal; - - BKE_scene_update_tagged(bmain->eval_ctx, bmain, win->screen->scene); - } - } - - CTX_wm_window_set(C, NULL); + wm_event_do_refresh_wm_and_depsgraph(C); } static int wm_event_always_pass(const wmEvent *event) @@ -523,14 +546,6 @@ int WM_operator_poll_context(bContext *C, wmOperatorType *ot, short context) return wm_operator_call_internal(C, ot, NULL, NULL, context, true); } -static void wm_operator_print(bContext *C, wmOperator *op) -{ - /* context is needed for enum function */ - char *buf = WM_operator_pystring(C, op, false, true); - puts(buf); - MEM_freeN(buf); -} - /** * Sets the active region for this space from the context. * @@ -610,9 +625,14 @@ void WM_report_banner_show(void) wm_reports->reporttimer->customdata = rti; } -bool WM_event_is_absolute(const wmEvent *event) +bool WM_event_is_last_mousemove(const wmEvent *event) { - return (event->tablet_data != NULL); + while ((event = event->next)) { + if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + return false; + } + } + return true; } #ifdef WITH_INPUT_NDOF @@ -684,12 +704,9 @@ static void wm_operator_reports(bContext *C, wmOperator *op, int retval, bool ca CTX_wm_region_set(C, ar_prev); } } - + if (retval & OPERATOR_FINISHED) { - if (G.debug & G_DEBUG_WM) { - /* todo - this print may double up, might want to check more flags then the FINISHED */ - wm_operator_print(C, op); - } + CLOG_STR_INFO_N(WM_LOG_OPERATORS, 1, WM_operator_pystring(C, op, false, true)); if (caller_owns_reports == false) { BKE_reports_print(op->reports, RPT_DEBUG); /* print out reports to console. */ @@ -715,15 +732,21 @@ static void wm_operator_reports(bContext *C, wmOperator *op, int retval, bool ca */ static bool wm_operator_register_check(wmWindowManager *wm, wmOperatorType *ot) { - return wm && (wm->op_undo_depth == 0) && (ot->flag & OPTYPE_REGISTER); + /* Check undo flag here since undo operators are also added to the list, + * to support checking if the same operator is run twice. */ + return wm && (wm->op_undo_depth == 0) && (ot->flag & (OPTYPE_REGISTER | OPTYPE_UNDO)); } -static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat) +static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat, const bool store) { wmWindowManager *wm = CTX_wm_manager(C); op->customdata = NULL; + if (store) { + WM_operator_last_properties_store(op); + } + /* we don't want to do undo pushes for operators that are being * called from operators that already do an undo push. usually * this will happen for python operators that call C operators */ @@ -769,14 +792,22 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons return retval; if (op->type->exec) { - if (op->type->flag & OPTYPE_UNDO) + if (op->type->flag & OPTYPE_UNDO) { wm->op_undo_depth++; + } + if (repeat) { + op->flag |= OP_IS_REPEAT; + } retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); + if (repeat) { + op->flag &= ~OP_IS_REPEAT; + } - if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) + if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) { wm->op_undo_depth--; + } } /* XXX Disabled the repeat check to address part 2 of #31840. @@ -786,12 +817,7 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons wm_operator_reports(C, op, retval, false); if (retval & OPERATOR_FINISHED) { - if (store) { - if (wm->op_undo_depth == 0) { /* not called by py script */ - WM_operator_last_properties_store(op); - } - } - wm_operator_finished(C, op, repeat); + wm_operator_finished(C, op, repeat, store && wm->op_undo_depth == 0); } else if (repeat == 0) { /* warning: modal from exec is bad practice, but avoid crashing. */ @@ -878,6 +904,20 @@ bool WM_operator_repeat_check(const bContext *UNUSED(C), wmOperator *op) return false; } +bool WM_operator_is_repeat(const bContext *C, const wmOperator *op) +{ + /* may be in the operators list or not */ + wmOperator *op_prev; + if (op->prev == NULL && op->next == NULL) { + wmWindowManager *wm = CTX_wm_manager(C); + op_prev = wm->operators.last; + } + else { + op_prev = op->prev; + } + return (op_prev && (op->type == op_prev->type)); +} + static wmOperator *wm_operator_create(wmWindowManager *wm, wmOperatorType *ot, PointerRNA *properties, ReportList *reports) { @@ -991,9 +1031,7 @@ bool WM_operator_last_properties_init(wmOperator *op) IDProperty *replaceprops = IDP_New(IDP_GROUP, &val, "wmOperatorProperties"); PropertyRNA *iterprop; - if (G.debug & G_DEBUG_WM) { - printf("%s: loading previous properties for '%s'\n", __func__, op->type->idname); - } + CLOG_INFO(WM_LOG_OPERATORS, 1, "loading previous properties for '%s'", op->type->idname); iterprop = RNA_struct_iterator_property(op->type->srna); @@ -1038,9 +1076,7 @@ bool WM_operator_last_properties_store(wmOperator *op) } if (op->properties) { - if (G.debug & G_DEBUG_WM) { - printf("%s: storing properties for '%s'\n", __func__, op->type->idname); - } + CLOG_INFO(WM_LOG_OPERATORS, 1, "storing properties for '%s'", op->type->idname); op->type->last_properties = IDP_CopyProperty(op->properties); return true; } @@ -1089,6 +1125,9 @@ void WM_operator_last_properties_alloc(struct wmOperator *op, const char *idname } } +/** + * Also used for exec when 'event' is NULL. + */ static int wm_operator_invoke( bContext *C, wmOperatorType *ot, wmEvent *event, PointerRNA *properties, ReportList *reports, const bool poll_only) @@ -1104,18 +1143,21 @@ static int wm_operator_invoke( wmOperator *op = wm_operator_create(wm, ot, properties, reports); /* if reports == NULL, they'll be initialized */ const bool is_nested_call = (wm->op_undo_depth != 0); - op->flag |= OP_IS_INVOKE; + if (event != NULL) { + op->flag |= OP_IS_INVOKE; + } /* initialize setting from previous run */ if (!is_nested_call) { /* not called by py script */ WM_operator_last_properties_init(op); } - if ((G.debug & G_DEBUG_HANDLERS) && ((event == NULL) || (event->type != MOUSEMOVE))) { - printf("%s: handle evt %d win %d op %s\n", - __func__, event ? event->type : 0, CTX_wm_screen(C)->subwinactive, ot->idname); + if ((event == NULL) || (event->type != MOUSEMOVE)) { + CLOG_INFO(WM_LOG_HANDLERS, 2, + "handle evt %d win %d op %s", + event ? event->type : 0, CTX_wm_screen(C)->subwinactive, ot->idname); } - + if (op->type->invoke && event) { wm_region_mouse_co(C, event); @@ -1140,9 +1182,9 @@ static int wm_operator_invoke( } else { /* debug, important to leave a while, should never happen */ - printf("%s: invalid operator call '%s'\n", __func__, ot->idname); + CLOG_ERROR(WM_LOG_OPERATORS, "invalid operator call '%s'", op->idname); } - + /* Note, if the report is given as an argument then assume the caller will deal with displaying them * currently python only uses this */ if (!(retval & OPERATOR_HANDLED) && (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED))) { @@ -1154,10 +1196,8 @@ static int wm_operator_invoke( /* do nothing, wm_operator_exec() has been called somewhere */ } else if (retval & OPERATOR_FINISHED) { - if (!is_nested_call) { /* not called by py script */ - WM_operator_last_properties_store(op); - } - wm_operator_finished(C, op, 0); + const bool store = !is_nested_call; + wm_operator_finished(C, op, false, store); } else if (retval & OPERATOR_RUNNING_MODAL) { /* take ownership of reports (in case python provided own) */ @@ -1252,6 +1292,8 @@ static int wm_operator_call_internal( switch (context) { case WM_OP_INVOKE_DEFAULT: case WM_OP_INVOKE_REGION_WIN: + case WM_OP_INVOKE_REGION_PREVIEW: + case WM_OP_INVOKE_REGION_CHANNELS: case WM_OP_INVOKE_AREA: case WM_OP_INVOKE_SCREEN: /* window is needed for invoke, cancel operator */ @@ -1378,6 +1420,19 @@ int WM_operator_name_call(bContext *C, const char *opstring, short context, Poin } /** + * Call an existent menu. The menu can be created in C or Python. + */ +void WM_menu_name_call(bContext *C, const char *menu_name, short context) +{ + wmOperatorType *ot = WM_operatortype_find("WM_OT_call_menu", false); + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + RNA_string_set(&ptr, "name", menu_name); + WM_operator_name_call_ptr(C, ot, context, &ptr); + WM_operator_properties_free(&ptr); +} + +/** * Similar to #WM_operator_name_call called with #WM_OP_EXEC_DEFAULT context. * * - #wmOperatorType is used instead of operator name since python already has the operator type. @@ -1404,8 +1459,10 @@ int WM_operator_call_py( if (is_undo && op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) wm->op_undo_depth--; } - else - printf("error \"%s\" operator has no exec function, python cannot call it\n", op->type->name); + else { + CLOG_WARN(WM_LOG_OPERATORS, "\"%s\" operator has no exec function, Python cannot call it", op->type->name); + } + #endif /* not especially nice using undo depth here, its used so py never @@ -1449,8 +1506,9 @@ static void wm_handler_op_context(bContext *C, wmEventHandler *handler, const wm if (sa == NULL) { /* when changing screen layouts with running modal handlers (like render display), this * is not an error to print */ - if (handler->op == NULL) - printf("internal error: handler (%s) has invalid area\n", handler->op->type->idname); + if (handler->op == NULL) { + CLOG_ERROR(WM_LOG_HANDLERS, "internal error: handler (%s) has invalid area", handler->op->type->idname); + } } else { ARegion *ar; @@ -1733,7 +1791,7 @@ static int wm_handler_operator_call(bContext *C, ListBase *handlers, wmEventHand /* important to run 'wm_operator_finished' before NULLing the context members */ if (retval & OPERATOR_FINISHED) { - wm_operator_finished(C, op, 0); + wm_operator_finished(C, op, false, true); handler->op = NULL; } else if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) { @@ -1763,10 +1821,9 @@ static int wm_handler_operator_call(bContext *C, ListBase *handlers, wmEventHand //retval &= ~OPERATOR_PASS_THROUGH; } } - } else { - printf("%s: error '%s' missing modal\n", __func__, op->idname); + CLOG_ERROR(WM_LOG_HANDLERS, "missing modal '%s'", op->idname); } } else { @@ -2042,7 +2099,7 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers PRINT("%s: checking '%s' ...", __func__, keymap->idname); - if (!keymap->poll || keymap->poll(C)) { + if (WM_keymap_poll(C, keymap)) { PRINT("pass\n"); @@ -2057,19 +2114,15 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers action |= wm_handler_operator_call(C, handlers, handler, event, kmi->ptr); if (action & WM_HANDLER_BREAK) { /* not always_pass here, it denotes removed handler */ - - if (G.debug & (G_DEBUG_EVENTS | G_DEBUG_HANDLERS)) - printf("%s: handled! '%s'\n", __func__, kmi->idname); - + CLOG_INFO(WM_LOG_HANDLERS, 2, "handled! '%s'", kmi->idname); break; } else { if (action & WM_HANDLER_HANDLED) { - if (G.debug & (G_DEBUG_EVENTS | G_DEBUG_HANDLERS)) - printf("%s: handled - and pass on! '%s'\n", __func__, kmi->idname); + CLOG_INFO(WM_LOG_HANDLERS, 2, "handled - and pass on! '%s'", kmi->idname); } else { - PRINT("%s: un-handled '%s'\n", __func__, kmi->idname); + CLOG_INFO(WM_LOG_HANDLERS, 2, "un-handled '%s'", kmi->idname); } } } @@ -2190,15 +2243,20 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers) (win->eventstate->prevval == KM_PRESS) && (win->eventstate->check_click == true)) { - event->val = KM_CLICK; - - if (G.debug & (G_DEBUG_HANDLERS)) { - printf("%s: handling CLICK\n", __func__); - } + if ((abs(event->x - win->eventstate->prevclickx)) <= WM_EVENT_CLICK_WIGGLE_ROOM && + (abs(event->y - win->eventstate->prevclicky)) <= WM_EVENT_CLICK_WIGGLE_ROOM) + { + event->val = KM_CLICK; - action |= wm_handlers_do_intern(C, event, handlers); + CLOG_INFO(WM_LOG_HANDLERS, 1, "handling CLICK"); + + action |= wm_handlers_do_intern(C, event, handlers); - event->val = KM_RELEASE; + event->val = KM_RELEASE; + } + else { + win->eventstate->check_click = 0; + } } else if (event->val == KM_DBL_CLICK) { event->val = KM_PRESS; @@ -2412,13 +2470,14 @@ void wm_event_do_handlers(bContext *C) if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS) && !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { printf("\n%s: Handling event\n", __func__); + WM_event_print(event); } /* take care of pie event filter */ if (wm_event_pie_filter(win, event)) { - if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS) && !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { - printf("\n%s: event filtered due to pie button pressed\n", __func__); + if (!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + CLOG_INFO(WM_LOG_HANDLERS, 1, "event filtered due to pie button pressed"); } BLI_remlink(&win->queue, event); wm_event_free(event); @@ -2427,6 +2486,13 @@ void wm_event_do_handlers(bContext *C) CTX_wm_window_set(C, win); + /* Clear tool-tip on mouse move. */ + if (win->screen->tool_tip && win->screen->tool_tip->exit_on_event) { + if (ISMOUSE(event->type)) { + WM_tooltip_clear(C, win); + } + } + /* we let modal handlers get active area/region, also wm_paintcursor_test needs it */ CTX_wm_area_set(C, area_event_inside(C, &event->x)); CTX_wm_region_set(C, region_event_inside(C, &event->x)); @@ -2443,7 +2509,17 @@ void wm_event_do_handlers(bContext *C) /* fileread case */ if (CTX_wm_window(C) == NULL) return; - + + /* check for a tooltip */ + { + bScreen *screen = CTX_wm_window(C)->screen; + if (screen->tool_tip && screen->tool_tip->timer) { + if ((event->type == TIMER) && (event->customdata == screen->tool_tip->timer)) { + WM_tooltip_init(C, win); + } + } + } + /* check dragging, creates new event or frees, adds draw tag */ wm_event_drag_test(wm, win, event); @@ -2685,7 +2761,7 @@ wmEventHandler *WM_event_add_keymap_handler(ListBase *handlers, wmKeyMap *keymap wmEventHandler *handler; if (!keymap) { - printf("%s: called with NULL keymap\n", __func__); + CLOG_WARN(WM_LOG_HANDLERS, "called with NULL keymap"); return NULL; } @@ -2853,7 +2929,7 @@ static void WM_event_remove_handler(ListBase *handlers, wmEventHandler *handler) } #endif -void WM_event_add_mousemove(bContext *C) +void WM_event_add_mousemove(const bContext *C) { wmWindow *window = CTX_wm_window(C); @@ -2862,7 +2938,7 @@ void WM_event_add_mousemove(bContext *C) /* for modal callbacks, check configuration for how to interpret exit with tweaks */ -bool WM_modal_tweak_exit(const wmEvent *event, int tweak_event) +bool WM_event_is_modal_tweak_exit(const wmEvent *event, int tweak_event) { /* if the release-confirm userpref setting is enabled, * tweak events can be canceled when mouse is released @@ -3147,8 +3223,9 @@ static bool wm_event_is_double_click(wmEvent *event, const wmEvent *event_state) (event_state->prevval == KM_RELEASE) && (event->val == KM_PRESS)) { - if ((ISMOUSE(event->type) == false) || ((ABS(event->x - event_state->prevclickx)) <= 2 && - (ABS(event->y - event_state->prevclicky)) <= 2)) + if ((ISMOUSE(event->type) == false) || + ((abs(event->x - event_state->prevclickx)) <= WM_EVENT_CLICK_WIGGLE_ROOM && + (abs(event->y - event_state->prevclicky)) <= WM_EVENT_CLICK_WIGGLE_ROOM)) { if ((PIL_check_seconds_timer() - event_state->prevclicktime) * 1000 < U.dbl_click_time) { return true; @@ -3159,7 +3236,7 @@ static bool wm_event_is_double_click(wmEvent *event, const wmEvent *event_state) return false; } -static void wm_event_add_mousemove(wmWindow *win, const wmEvent *event) +static wmEvent *wm_event_add_mousemove(wmWindow *win, const wmEvent *event) { wmEvent *event_last = win->queue.last; @@ -3169,16 +3246,13 @@ static void wm_event_add_mousemove(wmWindow *win, const wmEvent *event) if (event_last && event_last->type == MOUSEMOVE) event_last->type = INBETWEEN_MOUSEMOVE; - wm_event_add(win, event); - - { - wmEvent *event_new = win->queue.last; - if (event_last == NULL) { - event_last = win->eventstate; - } - - copy_v2_v2_int(&event_new->prevx, &event_last->x); + wmEvent *event_new = wm_event_add(win, event); + if (event_last == NULL) { + event_last = win->eventstate; } + + copy_v2_v2_int(&event_new->prevx, &event_last->x); + return event_new; } /* windows store own event queues, no bContext here */ @@ -3205,9 +3279,14 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U GHOST_TEventCursorData *cd = customdata; copy_v2_v2_int(&event.x, &cd->x); + wm_stereo3d_mouse_offset_apply(win, &event.x); + event.type = MOUSEMOVE; - wm_event_add_mousemove(win, &event); - copy_v2_v2_int(&evt->x, &event.x); + { + wmEvent *event_new = wm_event_add_mousemove(win, &event); + copy_v2_v2_int(&evt->x, &event_new->x); + evt->is_motion_absolute = event_new->is_motion_absolute; + } /* also add to other window if event is there, this makes overdraws disappear nicely */ /* it remaps mousecoord to other window in event */ @@ -3219,8 +3298,11 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U copy_v2_v2_int(&oevent.x, &event.x); oevent.type = MOUSEMOVE; - wm_event_add_mousemove(owin, &oevent); - copy_v2_v2_int(&oevt->x, &oevent.x); + { + wmEvent *event_new = wm_event_add_mousemove(owin, &oevent); + copy_v2_v2_int(&oevt->x, &event_new->x); + oevt->is_motion_absolute = event_new->is_motion_absolute; + } } break; @@ -3300,8 +3382,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U /* double click test */ if (wm_event_is_double_click(&event, evt)) { - if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) - printf("%s Send double click\n", __func__); + CLOG_INFO(WM_LOG_HANDLERS, 1, "Send double click"); event.val = KM_DBL_CLICK; } if (event.val == KM_PRESS) { @@ -3355,7 +3436,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U /* ghost should do this already for key up */ if (event.utf8_buf[0]) { - printf("%s: ghost on your platform is misbehaving, utf8 events on key up!\n", __func__); + CLOG_ERROR(WM_LOG_EVENTS, "ghost on your platform is misbehaving, utf8 events on key up!"); } event.utf8_buf[0] = '\0'; } @@ -3368,8 +3449,9 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U if (event.utf8_buf[0]) { if (BLI_str_utf8_size(event.utf8_buf) == -1) { - printf("%s: ghost detected an invalid unicode character '%d'!\n", - __func__, (int)(unsigned char)event.utf8_buf[0]); + CLOG_ERROR(WM_LOG_EVENTS, + "ghost detected an invalid unicode character '%d'", + (int)(unsigned char)event.utf8_buf[0]); event.utf8_buf[0] = '\0'; } } @@ -3418,8 +3500,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U /* double click test */ /* if previous event was same type, and previous was release, and now it presses... */ if (wm_event_is_double_click(&event, evt)) { - if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) - printf("%s Send double click\n", __func__); + CLOG_INFO(WM_LOG_HANDLERS, 1, "Send double click"); evt->val = event.val = KM_DBL_CLICK; } @@ -3489,9 +3570,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U attach_ndof_data(&event, customdata); wm_event_add(win, &event); - if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) - printf("%s sending NDOF_MOTION, prev = %d %d\n", __func__, event.x, event.y); - + CLOG_INFO(WM_LOG_HANDLERS, 1, "sending NDOF_MOTION, prev = %d %d", event.x, event.y); break; } diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index 05d63869074..da5ebd7abcd 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -62,6 +62,8 @@ #include "BLT_translation.h" +#include "BLF_api.h" + #include "DNA_mesh_types.h" /* only for USE_BMESH_SAVE_AS_COMPAT */ #include "DNA_object_types.h" #include "DNA_space_types.h" @@ -71,7 +73,6 @@ #include "DNA_windowmanager_types.h" #include "BKE_appdir.h" -#include "BKE_utildefines.h" #include "BKE_autoexec.h" #include "BKE_blender.h" #include "BKE_blendfile.h" @@ -86,9 +87,11 @@ #include "BKE_sound.h" #include "BKE_scene.h" #include "BKE_screen.h" +#include "BKE_undo_system.h" #include "BLO_readfile.h" #include "BLO_writefile.h" +#include "BLO_undofile.h" /* to save from an undo memfile */ #include "RNA_access.h" #include "RNA_define.h" @@ -102,6 +105,7 @@ #include "ED_screen.h" #include "ED_view3d.h" #include "ED_util.h" +#include "ED_undo.h" #include "GHOST_C-api.h" #include "GHOST_Path-api.h" @@ -316,10 +320,8 @@ static void wm_window_match_do(bContext *C, ListBase *oldwmlist) } /* in case UserDef was read, we re-initialize all, and do versioning */ -static void wm_init_userdef(bContext *C, const bool from_memory) +static void wm_init_userdef(Main *bmain, const bool read_userdef_from_memory) { - Main *bmain = CTX_data_main(C); - /* versioning is here */ UI_init_userdef(); @@ -327,23 +329,23 @@ static void wm_init_userdef(bContext *C, const bool from_memory) BKE_sound_init(bmain); /* needed so loading a file from the command line respects user-pref [#26156] */ - BKE_BIT_TEST_SET(G.fileflags, U.flag & USER_FILENOUI, G_FILE_NO_UI); + SET_FLAG_FROM_TEST(G.fileflags, U.flag & USER_FILENOUI, G_FILE_NO_UI); /* set the python auto-execute setting from user prefs */ /* enabled by default, unless explicitly enabled in the command line which overrides */ if ((G.f & G_SCRIPT_OVERRIDE_PREF) == 0) { - BKE_BIT_TEST_SET(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_SCRIPT_AUTOEXEC); + SET_FLAG_FROM_TEST(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_SCRIPT_AUTOEXEC); } /* avoid re-saving for every small change to our prefs, allow overrides */ - if (from_memory) { + if (read_userdef_from_memory) { BLO_update_defaults_userpref_blend(); } /* update tempdir from user preferences */ BKE_tempdir_init(U.tempdir); - BKE_blender_userdef_refresh(); + BLF_antialias_set((U.text_render & USER_TEXT_DISABLE_AA) == 0); } @@ -451,7 +453,7 @@ void wm_file_read_report(bContext *C) * Logic shared between #WM_file_read & #wm_homefile_read, * updates to make after reading a file. */ -static void wm_file_read_post(bContext *C, bool is_startup_file) +static void wm_file_read_post(bContext *C, const bool is_startup_file, const bool use_userdef) { bool addons_loaded = false; wmWindowManager *wm = CTX_wm_manager(C); @@ -470,9 +472,14 @@ static void wm_file_read_post(bContext *C, bool is_startup_file) if (is_startup_file) { /* possible python hasn't been initialized */ if (CTX_py_init_get(C)) { - /* sync addons, these may have changed from the defaults */ - BPY_execute_string(C, "__import__('addon_utils').reset_all()"); - + if (use_userdef) { + /* Only run when we have a template path found. */ + if (BKE_appdir_app_template_any()) { + BPY_execute_string(C, "__import__('bl_app_template_utils').reset()"); + } + /* sync addons, these may have changed from the defaults */ + BPY_execute_string(C, "__import__('addon_utils').reset_all()"); + } BPY_python_reset(C); addons_loaded = true; } @@ -482,6 +489,8 @@ static void wm_file_read_post(bContext *C, bool is_startup_file) BPY_python_reset(C); addons_loaded = true; } +#else + UNUSED_VARS(use_userdef); #endif /* WITH_PYTHON */ WM_operatortype_last_properties_clear_all(); @@ -516,9 +525,13 @@ static void wm_file_read_post(bContext *C, bool is_startup_file) } if (!G.background) { -// undo_editmode_clear(); - BKE_undo_reset(); - BKE_undo_write(C, "original"); /* save current state */ + if (wm->undo_stack == NULL) { + wm->undo_stack = BKE_undosys_stack_create(); + } + else { + BKE_undosys_stack_clear(wm->undo_stack); + } + BKE_undosys_stack_init_from_main(wm->undo_stack, CTX_data_main(C)); } } @@ -554,7 +567,7 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) /* confusing this global... */ G.relbase_valid = 1; - retval = BKE_blendfile_read(C, filepath, reports); + retval = BKE_blendfile_read(C, filepath, reports, 0); /* when loading startup.blend's, we can be left with a blank path */ if (G.main->name[0]) { G.save_over = 1; @@ -577,7 +590,7 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) if (retval == BKE_BLENDFILE_READ_OK_USERPREFS) { /* in case a userdef is read from regular .blend */ - wm_init_userdef(C, false); + wm_init_userdef(G.main, false); } if (retval != BKE_BLENDFILE_READ_FAIL) { @@ -586,14 +599,10 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) } } - wm_file_read_post(C, false); + wm_file_read_post(C, false, false); success = true; } -#if 0 - else if (retval == BKE_READ_EXOTIC_OK_OTHER) - BKE_undo_write(C, "Import file"); -#endif else if (retval == BKE_READ_EXOTIC_FAIL_OPEN) { BKE_reportf(reports, RPT_ERROR, "Cannot read file '%s': %s", filepath, errno ? strerror(errno) : TIP_("unable to open the file")); @@ -629,35 +638,50 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) /** - * called on startup, (context entirely filled with NULLs) - * or called for 'New File' - * both startup.blend and userpref.blend are checked - * the optional parameter custom_file points to an alternative startup page - * custom_file can be NULL + * Called on startup, (context entirely filled with NULLs) + * or called for 'New File' both startup.blend and userpref.blend are checked. + * + * \param use_factory_settings: Ignore on-disk startup file, use bundled ``datatoc_startup_blend`` instead. + * Used for "Restore Factory Settings". + * \param use_userdef: Load factory settings as well as startup file. + * Disabled for "File New" we don't want to reload preferences. + * \param filepath_startup_override: Optional path pointing to an alternative blend file (may be NULL). + * \param app_template_override: Template to use instead of the template defined in user-preferences. + * When not-null, this is written into the user preferences. */ -int wm_homefile_read(bContext *C, ReportList *reports, bool from_memory, const char *custom_file) +int wm_homefile_read( + bContext *C, ReportList *reports, + bool use_factory_settings, bool use_empty_data, bool use_userdef, + const char *filepath_startup_override, const char *app_template_override) { ListBase wmbase; - char startstr[FILE_MAX]; - char prefstr[FILE_MAX]; - int success = 0; + bool success = false; + + char filepath_startup[FILE_MAX]; + char filepath_userdef[FILE_MAX]; + + /* When 'app_template' is set: '{BLENDER_USER_CONFIG}/{app_template}' */ + char app_template_system[FILE_MAX]; + /* When 'app_template' is set: '{BLENDER_SYSTEM_SCRIPTS}/startup/bl_app_templates_system/{app_template}' */ + char app_template_config[FILE_MAX]; /* Indicates whether user preferences were really load from memory. * - * This is used for versioning code, and for this we can not rely on from_memory + * This is used for versioning code, and for this we can not rely on use_factory_settings * passed via argument. This is because there might be configuration folder * exists but it might not have userpref.blend and in this case we fallback to * reading home file from memory. * * And in this case versioning code is to be run. */ - bool read_userdef_from_memory = true; + bool read_userdef_from_memory = false; + eBLOReadSkip skip_flags = use_userdef ? 0 : BLO_READ_SKIP_USERDEF; /* options exclude eachother */ - BLI_assert((from_memory && custom_file) == 0); + BLI_assert((use_factory_settings && filepath_startup_override) == 0); if ((G.f & G_SCRIPT_OVERRIDE_PREF) == 0) { - BKE_BIT_TEST_SET(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_SCRIPT_AUTOEXEC); + SET_FLAG_FROM_TEST(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_SCRIPT_AUTOEXEC); } BLI_callback_exec(CTX_data_main(C), NULL, BLI_CB_EVT_LOAD_PRE); @@ -665,77 +689,166 @@ int wm_homefile_read(bContext *C, ReportList *reports, bool from_memory, const c UI_view2d_zoom_cache_reset(); G.relbase_valid = 0; - if (!from_memory) { - const char * const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL); - if (custom_file) { - BLI_strncpy(startstr, custom_file, FILE_MAX); - if (cfgdir) { - BLI_make_file_string(G.main->name, prefstr, cfgdir, BLENDER_USERPREF_FILE); + /* put aside screens to match with persistent windows later */ + wm_window_match_init(C, &wmbase); + + filepath_startup[0] = '\0'; + filepath_userdef[0] = '\0'; + app_template_system[0] = '\0'; + app_template_config[0] = '\0'; + + const char * const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL); + if (!use_factory_settings) { + if (cfgdir) { + BLI_path_join(filepath_startup, sizeof(filepath_startup), cfgdir, BLENDER_STARTUP_FILE, NULL); + if (use_userdef) { + BLI_path_join(filepath_userdef, sizeof(filepath_startup), cfgdir, BLENDER_USERPREF_FILE, NULL); } - else { - prefstr[0] = '\0'; + } + else { + use_factory_settings = true; + } + + if (filepath_startup_override) { + BLI_strncpy(filepath_startup, filepath_startup_override, FILE_MAX); + } + } + + /* load preferences before startup.blend */ + if (use_userdef) { + if (!use_factory_settings && BLI_exists(filepath_userdef)) { + UserDef *userdef = BKE_blendfile_userdef_read(filepath_userdef, NULL); + if (userdef != NULL) { + BKE_blender_userdef_data_set_and_free(userdef); + userdef = NULL; + + skip_flags |= BLO_READ_SKIP_USERDEF; + printf("Read prefs: %s\n", filepath_userdef); } } - else if (cfgdir) { - BLI_make_file_string(G.main->name, startstr, cfgdir, BLENDER_STARTUP_FILE); - BLI_make_file_string(G.main->name, prefstr, cfgdir, BLENDER_USERPREF_FILE); + } + + const char *app_template = NULL; + + if (filepath_startup_override != NULL) { + /* pass */ + } + else if (app_template_override) { + /* This may be clearing the current template by setting to an empty string. */ + app_template = app_template_override; + } + else if (!use_factory_settings && U.app_template[0]) { + app_template = U.app_template; + } + + if ((app_template != NULL) && (app_template[0] != '\0')) { + BKE_appdir_app_template_id_search(app_template, app_template_system, sizeof(app_template_system)); + + /* Insert template name into startup file. */ + + /* note that the path is being set even when 'use_factory_settings == true' + * this is done so we can load a templates factory-settings */ + if (!use_factory_settings) { + BLI_path_join(app_template_config, sizeof(app_template_config), cfgdir, app_template, NULL); + BLI_path_join(filepath_startup, sizeof(filepath_startup), app_template_config, BLENDER_STARTUP_FILE, NULL); + if (BLI_access(filepath_startup, R_OK) != 0) { + filepath_startup[0] = '\0'; + } } else { - startstr[0] = '\0'; - prefstr[0] = '\0'; - from_memory = 1; + filepath_startup[0] = '\0'; + } + + if (filepath_startup[0] == '\0') { + BLI_path_join(filepath_startup, sizeof(filepath_startup), app_template_system, BLENDER_STARTUP_FILE, NULL); } } - - /* put aside screens to match with persistent windows later */ - wm_window_match_init(C, &wmbase); - - if (!from_memory) { - if (BLI_access(startstr, R_OK) == 0) { - success = (BKE_blendfile_read(C, startstr, NULL) != BKE_BLENDFILE_READ_FAIL); + + if (!use_factory_settings || (filepath_startup[0] != '\0')) { + if (BLI_access(filepath_startup, R_OK) == 0) { + success = (BKE_blendfile_read(C, filepath_startup, NULL, skip_flags) != BKE_BLENDFILE_READ_FAIL); } if (BLI_listbase_is_empty(&U.themes)) { if (G.debug & G_DEBUG) - printf("\nNote: No (valid) '%s' found, fall back to built-in default.\n\n", startstr); - success = 0; + printf("\nNote: No (valid) '%s' found, fall back to built-in default.\n\n", filepath_startup); + success = false; } } - if (success == 0 && custom_file && reports) { - BKE_reportf(reports, RPT_ERROR, "Could not read '%s'", custom_file); - /*We can not return from here because wm is already reset*/ + if (success == false && filepath_startup_override && reports) { + /* We can not return from here because wm is already reset */ + BKE_reportf(reports, RPT_ERROR, "Could not read '%s'", filepath_startup_override); } - if (success == 0) { - success = BKE_blendfile_read_from_memory(C, datatoc_startup_blend, datatoc_startup_blend_size, NULL, true); + if (success == false) { + success = BKE_blendfile_read_from_memory( + C, datatoc_startup_blend, datatoc_startup_blend_size, + NULL, skip_flags, true); + if (success) { + if (use_userdef) { + if ((skip_flags & BLO_READ_SKIP_USERDEF) == 0) { + read_userdef_from_memory = true; + } + } + } if (BLI_listbase_is_empty(&wmbase)) { wm_clear_default_size(C); } - BKE_tempdir_init(U.tempdir); + } -#ifdef WITH_PYTHON_SECURITY - /* use alternative setting for security nuts - * otherwise we'd need to patch the binary blob - startup.blend.c */ - U.flag |= USER_SCRIPT_AUTOEXEC_DISABLE; -#endif + if (use_empty_data) { + BKE_blendfile_read_make_empty(C); } - - /* check new prefs only after startup.blend was finished */ - if (!from_memory && BLI_exists(prefstr)) { - int done = BKE_blendfile_read_userdef(prefstr, NULL); - if (done != BKE_BLENDFILE_READ_FAIL) { - read_userdef_from_memory = false; - printf("Read new prefs: %s\n", prefstr); + + /* Load template preferences, + * unlike regular preferences we only use some of the settings, + * see: BKE_blender_userdef_set_app_template */ + if (app_template_system[0] != '\0') { + char temp_path[FILE_MAX]; + temp_path[0] = '\0'; + if (!use_factory_settings) { + BLI_path_join(temp_path, sizeof(temp_path), app_template_config, BLENDER_USERPREF_FILE, NULL); + if (BLI_access(temp_path, R_OK) != 0) { + temp_path[0] = '\0'; + } + } + + if (temp_path[0] == '\0') { + BLI_path_join(temp_path, sizeof(temp_path), app_template_system, BLENDER_USERPREF_FILE, NULL); + } + + if (use_userdef) { + UserDef *userdef_template = NULL; + /* just avoids missing file warning */ + if (BLI_exists(temp_path)) { + userdef_template = BKE_blendfile_userdef_read(temp_path, NULL); + } + if (userdef_template == NULL) { + /* we need to have preferences load to overwrite preferences from previous template */ + userdef_template = BKE_blendfile_userdef_read_from_memory( + datatoc_startup_blend, datatoc_startup_blend_size, NULL); + read_userdef_from_memory = true; + } + if (userdef_template) { + BKE_blender_userdef_app_template_data_set_and_free(userdef_template); + userdef_template = NULL; + } } } - + + if (app_template_override) { + BLI_strncpy(U.app_template, app_template_override, sizeof(U.app_template)); + } + /* prevent buggy files that had G_FILE_RELATIVE_REMAP written out by mistake. Screws up autosaves otherwise * can remove this eventually, only in a 2.53 and older, now its not written */ G.fileflags &= ~G_FILE_RELATIVE_REMAP; - /* check userdef before open window, keymaps etc */ - wm_init_userdef(C, read_userdef_from_memory); + if (use_userdef) { + /* check userdef before open window, keymaps etc */ + wm_init_userdef(CTX_data_main(C), read_userdef_from_memory); + } /* match the read WM with current WM */ wm_window_match_do(C, &wmbase); @@ -743,14 +856,19 @@ int wm_homefile_read(bContext *C, ReportList *reports, bool from_memory, const c G.main->name[0] = '\0'; - /* When loading factory settings, the reset solid OpenGL lights need to be applied. */ - if (!G.background) GPU_default_lights(); - - /* XXX */ - G.save_over = 0; // start with save preference untitled.blend - G.fileflags &= ~G_FILE_AUTOPLAY; /* disable autoplay in startup.blend... */ + if (use_userdef) { + /* When loading factory settings, the reset solid OpenGL lights need to be applied. */ + if (!G.background) { + GPU_default_lights(); + } + } + + /* start with save preference untitled.blend */ + G.save_over = 0; + /* disable auto-play in startup.blend... */ + G.fileflags &= ~G_FILE_AUTOPLAY; - wm_file_read_post(C, true); + wm_file_read_post(C, true, use_userdef); return true; } @@ -918,14 +1036,14 @@ static ImBuf *blend_file_thumb(Scene *scene, bScreen *screen, BlendThumbnail **t ibuf = ED_view3d_draw_offscreen_imbuf_simple( scene, scene->camera, BLEN_THUMB_SIZE * 2, BLEN_THUMB_SIZE * 2, - IB_rect, OB_SOLID, false, false, false, R_ALPHAPREMUL, 0, false, NULL, + IB_rect, V3D_OFSDRAW_NONE, OB_SOLID, R_ALPHAPREMUL, 0, NULL, NULL, NULL, err_out); } else { ibuf = ED_view3d_draw_offscreen_imbuf( scene, v3d, ar, BLEN_THUMB_SIZE * 2, BLEN_THUMB_SIZE * 2, - IB_rect, false, R_ALPHAPREMUL, 0, false, NULL, + IB_rect, V3D_OFSDRAW_NONE, R_ALPHAPREMUL, 0, NULL, NULL, NULL, err_out); } @@ -998,7 +1116,7 @@ static int wm_file_write(bContext *C, const char *filepath, int fileflags, Repor BKE_reportf(reports, RPT_ERROR, "Cannot save blend file, path '%s' is not writable", filepath); return ret; } - + /* note: used to replace the file extension (to ensure '.blend'), * no need to now because the operator ensures, * its handy for scripts to save to a predefined name without blender editing it */ @@ -1054,8 +1172,8 @@ static int wm_file_write(bContext *C, const char *filepath, int fileflags, Repor G.save_over = 1; /* disable untitled.blend convention */ } - BKE_BIT_TEST_SET(G.fileflags, fileflags & G_FILE_COMPRESS, G_FILE_COMPRESS); - BKE_BIT_TEST_SET(G.fileflags, fileflags & G_FILE_AUTOPLAY, G_FILE_AUTOPLAY); + SET_FLAG_FROM_TEST(G.fileflags, fileflags & G_FILE_COMPRESS, G_FILE_COMPRESS); + SET_FLAG_FROM_TEST(G.fileflags, fileflags & G_FILE_AUTOPLAY, G_FILE_AUTOPLAY); /* prevent background mode scripts from clobbering history */ if (do_history) { @@ -1156,7 +1274,10 @@ void wm_autosave_timer(const bContext *C, wmWindowManager *wm, wmTimer *UNUSED(w if (U.uiflag & USER_GLOBALUNDO) { /* fast save of last undobuffer, now with UI */ - BKE_undo_save_file(filepath); + struct MemFile *memfile = ED_undosys_stack_memfile_get_active(wm->undo_stack); + if (memfile) { + BLO_memfile_write_file(memfile, filepath); + } } else { /* save as regular blend file */ @@ -1240,13 +1361,13 @@ void wm_open_init_use_scripts(wmOperator *op, bool use_prefs) /** \} */ -void WM_file_tag_modified(const bContext *C) +void WM_file_tag_modified(void) { - wmWindowManager *wm = CTX_wm_manager(C); + wmWindowManager *wm = G.main->wm.first; if (wm->file_saved) { wm->file_saved = 0; /* notifier that data changed, for save-over warning or header */ - WM_event_add_notifier(C, NC_WM | ND_DATACHANGED, NULL); + WM_main_add_notifier(NC_WM | ND_DATACHANGED, NULL); } } @@ -1264,6 +1385,13 @@ static int wm_homefile_write_exec(bContext *C, wmOperator *op) char filepath[FILE_MAX]; int fileflags; + const char *app_template = U.app_template[0] ? U.app_template : NULL; + const char * const cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, app_template); + if (cfgdir == NULL) { + BKE_report(op->reports, RPT_ERROR, "Unable to create user config path"); + return OPERATOR_CANCELLED; + } + BLI_callback_exec(G.main, NULL, BLI_CB_EVT_SAVE_PRE); /* check current window and close it if temp */ @@ -1273,7 +1401,8 @@ static int wm_homefile_write_exec(bContext *C, wmOperator *op) /* update keymaps in user preferences */ WM_keyconfig_update(wm); - BLI_make_file_string("/", filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_STARTUP_FILE); + BLI_path_join(filepath, sizeof(filepath), cfgdir, BLENDER_STARTUP_FILE, NULL); + printf("trying to save homefile at %s ", filepath); ED_editors_flush_edits(C, false); @@ -1351,21 +1480,56 @@ static int wm_userpref_write_exec(bContext *C, wmOperator *op) { wmWindowManager *wm = CTX_wm_manager(C); char filepath[FILE_MAX]; + const char *cfgdir; + bool ok = true; /* update keymaps in user preferences */ WM_keyconfig_update(wm); - BLI_make_file_string("/", filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_USERPREF_FILE); - printf("trying to save userpref at %s ", filepath); + if ((cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL))) { + bool ok_write; + BLI_path_join(filepath, sizeof(filepath), cfgdir, BLENDER_USERPREF_FILE, NULL); + printf("trying to save userpref at %s ", filepath); - if (BKE_blendfile_write_userdef(filepath, op->reports) == 0) { - printf("fail\n"); - return OPERATOR_CANCELLED; + if (U.app_template[0]) { + ok_write = BKE_blendfile_userdef_write_app_template(filepath, op->reports); + } + else { + ok_write = BKE_blendfile_userdef_write(filepath, op->reports); + } + + if (ok_write) { + printf("ok\n"); + } + else { + printf("fail\n"); + ok = false; + } + } + else { + BKE_report(op->reports, RPT_ERROR, "Unable to create userpref path"); } - printf("ok\n"); + if (U.app_template[0]) { + if ((cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, U.app_template))) { + /* Also save app-template prefs */ + BLI_path_join(filepath, sizeof(filepath), cfgdir, BLENDER_USERPREF_FILE, NULL); + printf("trying to save app-template userpref at %s ", filepath); + if (BKE_blendfile_userdef_write(filepath, op->reports) != 0) { + printf("ok\n"); + } + else { + printf("fail\n"); + ok = false; + } + } + else { + BKE_report(op->reports, RPT_ERROR, "Unable to create app-template userpref path"); + ok = false; + } + } - return OPERATOR_FINISHED; + return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void WM_OT_save_userpref(wmOperatorType *ot) @@ -1400,17 +1564,18 @@ void WM_OT_read_history(wmOperatorType *ot) static int wm_homefile_read_exec(bContext *C, wmOperator *op) { - const bool from_memory = (STREQ(op->type->idname, "WM_OT_read_factory_settings")); + const bool use_factory_settings = (STREQ(op->type->idname, "WM_OT_read_factory_settings")); + bool use_userdef = false; char filepath_buf[FILE_MAX]; const char *filepath = NULL; - if (!from_memory) { + if (!use_factory_settings) { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "filepath"); /* This can be used when loading of a start-up file should only change * the scene content but keep the blender UI as it is. */ wm_open_init_load_ui(op, true); - BKE_BIT_TEST_SET(G.fileflags, !RNA_boolean_get(op->ptr, "load_ui"), G_FILE_NO_UI); + SET_FLAG_FROM_TEST(G.fileflags, !RNA_boolean_get(op->ptr, "load_ui"), G_FILE_NO_UI); if (RNA_property_is_set(op->ptr, prop)) { RNA_property_string_get(op->ptr, prop, filepath_buf); @@ -1424,9 +1589,36 @@ static int wm_homefile_read_exec(bContext *C, wmOperator *op) else { /* always load UI for factory settings (prefs will re-init) */ G.fileflags &= ~G_FILE_NO_UI; + /* Always load preferences with factory settings. */ + use_userdef = true; + } + + char app_template_buf[sizeof(U.app_template)]; + const char *app_template; + PropertyRNA *prop_app_template = RNA_struct_find_property(op->ptr, "app_template"); + const bool use_splash = !use_factory_settings && RNA_boolean_get(op->ptr, "use_splash"); + const bool use_empty_data = RNA_boolean_get(op->ptr, "use_empty"); + + if (prop_app_template && RNA_property_is_set(op->ptr, prop_app_template)) { + RNA_property_string_get(op->ptr, prop_app_template, app_template_buf); + app_template = app_template_buf; + + /* Always load preferences when switching templates. */ + use_userdef = true; + } + else { + app_template = NULL; } - return wm_homefile_read(C, op->reports, from_memory, filepath) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + if (wm_homefile_read(C, op->reports, use_factory_settings, use_empty_data, use_userdef, filepath, app_template)) { + if (use_splash) { + WM_init_splash(C); + } + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } } void WM_OT_read_homefile(wmOperatorType *ot) @@ -1449,17 +1641,36 @@ void WM_OT_read_homefile(wmOperatorType *ot) "Load user interface setup from the .blend file"); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "use_empty", false, "Empty", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + /* So the splash can be kept open after loading a file (for templates). */ + prop = RNA_def_boolean(ot->srna, "use_splash", false, "Splash", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + prop = RNA_def_string(ot->srna, "app_template", "Template", sizeof(U.app_template), "", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + /* omit poll to run in background mode */ } void WM_OT_read_factory_settings(wmOperatorType *ot) { + PropertyRNA *prop; + ot->name = "Load Factory Settings"; ot->idname = "WM_OT_read_factory_settings"; ot->description = "Load default file and user preferences"; ot->invoke = WM_operator_confirm; ot->exec = wm_homefile_read_exec; + + prop = RNA_def_string(ot->srna, "app_template", "Template", sizeof(U.app_template), "", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + prop = RNA_def_boolean(ot->srna, "use_empty", false, "Empty", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + /* omit poll to run in background mode */ } @@ -1847,20 +2058,24 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op) fileflags = G.fileflags & ~G_FILE_USERPREFS; /* set compression flag */ - BKE_BIT_TEST_SET(fileflags, RNA_boolean_get(op->ptr, "compress"), - G_FILE_COMPRESS); - BKE_BIT_TEST_SET(fileflags, RNA_boolean_get(op->ptr, "relative_remap"), - G_FILE_RELATIVE_REMAP); - BKE_BIT_TEST_SET(fileflags, - (RNA_struct_property_is_set(op->ptr, "copy") && - RNA_boolean_get(op->ptr, "copy")), - G_FILE_SAVE_COPY); + SET_FLAG_FROM_TEST( + fileflags, RNA_boolean_get(op->ptr, "compress"), + G_FILE_COMPRESS); + SET_FLAG_FROM_TEST( + fileflags, RNA_boolean_get(op->ptr, "relative_remap"), + G_FILE_RELATIVE_REMAP); + SET_FLAG_FROM_TEST( + fileflags, + (RNA_struct_property_is_set(op->ptr, "copy") && + RNA_boolean_get(op->ptr, "copy")), + G_FILE_SAVE_COPY); #ifdef USE_BMESH_SAVE_AS_COMPAT - BKE_BIT_TEST_SET(fileflags, - (RNA_struct_find_property(op->ptr, "use_mesh_compat") && - RNA_boolean_get(op->ptr, "use_mesh_compat")), - G_FILE_MESH_COMPAT); + SET_FLAG_FROM_TEST( + fileflags, + (RNA_struct_find_property(op->ptr, "use_mesh_compat") && + RNA_boolean_get(op->ptr, "use_mesh_compat")), + G_FILE_MESH_COMPAT); #else # error "don't remove by accident" #endif @@ -1870,6 +2085,10 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_WM | ND_FILESAVE, NULL); + if (RNA_boolean_get(op->ptr, "exit")) { + wm_exit_schedule_delayed(C); + } + return OPERATOR_FINISHED; } @@ -1941,11 +2160,13 @@ static int wm_save_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *U char path[FILE_MAX]; RNA_string_get(op->ptr, "filepath", path); - if (BLI_exists(path)) { + if (RNA_boolean_get(op->ptr, "check_existing") && BLI_exists(path)) { ret = WM_operator_confirm_message_ex(C, op, IFACE_("Save Over?"), ICON_QUESTION, path); } else { ret = wm_save_as_mainfile_exec(C, op); + /* Without this there is no feedback the file was saved. */ + BKE_reportf(op->reports, RPT_INFO, "Saved \"%s\"", BLI_path_basename(path)); } } else { @@ -1967,12 +2188,16 @@ void WM_OT_save_mainfile(wmOperatorType *ot) ot->check = blend_save_check; /* omit window poll so this can work in background mode */ + PropertyRNA *prop; WM_operator_properties_filesel( ot, FILE_TYPE_FOLDER | FILE_TYPE_BLENDER, FILE_BLENDER, FILE_SAVE, WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY, FILE_SORT_ALPHA); RNA_def_boolean(ot->srna, "compress", false, "Compress", "Write compressed .blend file"); RNA_def_boolean(ot->srna, "relative_remap", false, "Remap Relative", "Remap relative paths when saving in a different directory"); + + prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "Exit Blender after saving"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /** \} */ diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index 3b733f9558c..fb611290aa5 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -157,7 +157,7 @@ typedef struct WMLinkAppendData { LinkNodePair items; int num_libraries; int num_items; - short flag; + int flag; /* Combines eFileSel_Params_Flag from DNA_space_types.h and BLO_LibLinkFlags from BLO_readfile.h */ /* Internal 'private' data */ MemArena *memarena; @@ -211,9 +211,7 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add( return item; } -static void wm_link_do( - WMLinkAppendData *lapp_data, ReportList *reports, Main *bmain, Scene *scene, View3D *v3d, - const bool use_placeholders, const bool force_indirect) +static void wm_link_do(WMLinkAppendData *lapp_data, ReportList *reports, Main *bmain, Scene *scene, View3D *v3d) { Main *mainl; BlendHandle *bh; @@ -260,8 +258,7 @@ static void wm_link_do( continue; } - new_id = BLO_library_link_named_part_ex( - mainl, &bh, item->idcode, item->name, flag, scene, v3d, use_placeholders, force_indirect); + new_id = BLO_library_link_named_part_ex(mainl, &bh, item->idcode, item->name, flag, scene, v3d); if (new_id) { /* If the link is successful, clear item's libs 'todo' flags. @@ -282,10 +279,9 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); PropertyRNA *prop; WMLinkAppendData *lapp_data; - char path[FILE_MAX_LIBEXTRA], root[FILE_MAXDIR], libname[FILE_MAX], relname[FILE_MAX]; + char path[FILE_MAX_LIBEXTRA], root[FILE_MAXDIR], libname[FILE_MAX_LIBEXTRA], relname[FILE_MAX]; char *group, *name; int totfiles = 0; - short flag; RNA_string_get(op->ptr, "filename", relname); RNA_string_get(op->ptr, "directory", root); @@ -322,7 +318,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - flag = wm_link_append_flag(op); + short flag = wm_link_append_flag(op); /* sanity checks for flag */ if (scene && scene->id.lib) { @@ -332,6 +328,8 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) scene = NULL; } + /* We need to add nothing from BLO_LibLinkFlags to flag here. */ + /* from here down, no error returns */ if (scene && RNA_boolean_get(op->ptr, "autoselect")) { @@ -402,10 +400,16 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) BLI_BITMAP_ENABLE(item->libraries, 0); } + if (lapp_data->num_items == 0) { + /* Early out in case there is nothing to link. */ + wm_link_append_data_free(lapp_data); + return OPERATOR_CANCELLED; + } + /* XXX We'd need re-entrant locking on Main for this to work... */ /* BKE_main_lock(bmain); */ - wm_link_do(lapp_data, op->reports, bmain, scene, CTX_wm_view3d(C), false, false); + wm_link_do(lapp_data, op->reports, bmain, scene, CTX_wm_view3d(C)); /* BKE_main_unlock(bmain); */ @@ -446,7 +450,9 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); /* recreate dependency graph to include new objects */ - DAG_scene_relations_rebuild(bmain, scene); + if (scene) { + DAG_scene_relations_rebuild(bmain, scene); + } /* free gpu materials, some materials depend on existing objects, such as lamps so freeing correctly refreshes */ GPU_materials_free(); @@ -589,10 +595,15 @@ static void lib_relocate_do( } } + if (lapp_data->num_items == 0) { + /* Early out in case there is nothing to do. */ + return; + } + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); /* We do not want any instanciation here! */ - wm_link_do(lapp_data, reports, bmain, NULL, NULL, do_reload, do_reload); + wm_link_do(lapp_data, reports, bmain, NULL, NULL); BKE_main_lock(bmain); @@ -608,7 +619,8 @@ static void lib_relocate_do( } /* Note that in reload case, we also want to replace indirect usages. */ - const short remap_flags = ID_REMAP_SKIP_NEVER_NULL_USAGE | (do_reload ? 0 : ID_REMAP_SKIP_INDIRECT_USAGE); + const short remap_flags = ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_NO_INDIRECT_PROXY_DATA_USAGE | + (do_reload ? 0 : ID_REMAP_SKIP_INDIRECT_USAGE); for (item_idx = 0, itemlink = lapp_data->items.list; itemlink; item_idx++, itemlink = itemlink->next) { WMLinkAppendDataItem *item = itemlink->link; ID *old_id = item->customdata; @@ -755,7 +767,7 @@ void WM_lib_reload(Library *lib, bContext *C, ReportList *reports) return; } - WMLinkAppendData *lapp_data = wm_link_append_data_new(0); + WMLinkAppendData *lapp_data = wm_link_append_data_new(BLO_LIBLINK_USE_PLACEHOLDERS | BLO_LIBLINK_FORCE_INDIRECT); wm_link_append_data_library_add(lapp_data, lib->filepath); @@ -866,6 +878,10 @@ static int wm_lib_relocate_exec_do(bContext *C, wmOperator *op, bool do_reload) } } + if (do_reload) { + lapp_data->flag |= BLO_LIBLINK_USE_PLACEHOLDERS | BLO_LIBLINK_FORCE_INDIRECT; + } + lib_relocate_do(bmain, scene, lib, lapp_data, op->reports, do_reload); wm_link_append_data_free(lapp_data); diff --git a/source/blender/windowmanager/intern/wm_gesture.c b/source/blender/windowmanager/intern/wm_gesture.c index 46203333eb5..1a4381a1275 100644 --- a/source/blender/windowmanager/intern/wm_gesture.c +++ b/source/blender/windowmanager/intern/wm_gesture.c @@ -41,7 +41,7 @@ #include "BLI_blenlib.h" #include "BLI_math.h" #include "BLI_utildefines.h" -#include "BLI_lasso.h" +#include "BLI_lasso_2d.h" #include "BKE_context.h" @@ -71,6 +71,8 @@ wmGesture *WM_gesture_new(bContext *C, const wmEvent *event, int type) gesture->type = type; gesture->event_type = event->type; gesture->swinid = ar->swinid; /* means only in area-region context! */ + gesture->userdata_free = true; /* Free if userdata is set. */ + gesture->modal_state = GESTURE_MODAL_NOP; wm_subwindow_origin_get(window, gesture->swinid, &sx, &sy); @@ -83,11 +85,7 @@ wmGesture *WM_gesture_new(bContext *C, const wmEvent *event, int type) rect->xmin = event->x - sx; rect->ymin = event->y - sy; if (type == WM_GESTURE_CIRCLE) { -#ifdef GESTURE_MEMORY - rect->xmax = circle_select_size; -#else - rect->xmax = 25; // XXX temp -#endif + /* caller is responsible for initializing 'xmax' to radius. */ } else { rect->xmax = event->x - sx; @@ -96,11 +94,11 @@ wmGesture *WM_gesture_new(bContext *C, const wmEvent *event, int type) } else if (ELEM(type, WM_GESTURE_LINES, WM_GESTURE_LASSO)) { short *lasso; - gesture->customdata = lasso = MEM_callocN(2 * sizeof(short) * WM_LASSO_MIN_POINTS, "lasso points"); + gesture->points_alloc = 1024; + gesture->customdata = lasso = MEM_mallocN(sizeof(short[2]) * gesture->points_alloc, "lasso points"); lasso[0] = event->x - sx; lasso[1] = event->y - sy; gesture->points = 1; - gesture->size = WM_LASSO_MIN_POINTS; } return gesture; @@ -114,7 +112,7 @@ void WM_gesture_end(bContext *C, wmGesture *gesture) win->tweak = NULL; BLI_remlink(&win->gesture, gesture); MEM_freeN(gesture->customdata); - if (gesture->userdata) { + if (gesture->userdata && gesture->userdata_free) { MEM_freeN(gesture->userdata); } MEM_freeN(gesture); @@ -137,7 +135,7 @@ int wm_gesture_evaluate(wmGesture *gesture) int dx = BLI_rcti_size_x(rect); int dy = BLI_rcti_size_y(rect); if (abs(dx) + abs(dy) > U.tweak_threshold) { - int theta = iroundf(4.0f * atan2f((float)dy, (float)dx) / (float)M_PI); + int theta = round_fl_to_int(4.0f * atan2f((float)dy, (float)dx) / (float)M_PI); int val = EVT_GESTURE_W; if (theta == 0) val = EVT_GESTURE_E; @@ -376,10 +374,12 @@ void wm_gesture_draw(wmWindow *win) else if (gt->type == WM_GESTURE_CIRCLE) wm_gesture_draw_circle(gt); else if (gt->type == WM_GESTURE_CROSS_RECT) { - if (gt->mode == 1) + if (gt->is_active) { wm_gesture_draw_rect(gt); - else + } + else { wm_gesture_draw_cross(win, gt); + } } else if (gt->type == WM_GESTURE_LINES) wm_gesture_draw_lasso(win, gt, false); diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c new file mode 100644 index 00000000000..a554727cacd --- /dev/null +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -0,0 +1,883 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2007 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/intern/wm_gesture_ops.c + * \ingroup wm + * + * Default operator callbacks for use with gestures (border/circle/lasso/straightline). + * Operators themselves are defined elsewhere. + * + * - Keymaps are in ``wm_operators.c``. + * - Property definitions are in ``wm_operator_props.c``. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_windowmanager_types.h" + +#include "BLI_math.h" + +#include "BKE_context.h" + +#include "WM_types.h" +#include "WM_api.h" + +#include "wm.h" +#include "wm_event_types.h" +#include "wm_event_system.h" +#include "wm_subwindow.h" + +#include "ED_screen.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +/* -------------------------------------------------------------------- */ +/** \name Internal Gesture Utilities + * + * Border gesture has two types: + * -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border. + * -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends. + * + * It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type). + * + * \{ */ + +static void gesture_modal_end(bContext *C, wmOperator *op) +{ + wmGesture *gesture = op->customdata; + + WM_gesture_end(C, gesture); /* frees gesture itself, and unregisters from window */ + op->customdata = NULL; + + ED_area_tag_redraw(CTX_wm_area(C)); + + if (RNA_struct_find_property(op->ptr, "cursor")) { + WM_cursor_modal_restore(CTX_wm_window(C)); + } +} + +static void gesture_modal_state_to_operator(wmOperator *op, int modal_state) +{ + PropertyRNA *prop; + + switch (modal_state) { + case GESTURE_MODAL_SELECT: + case GESTURE_MODAL_DESELECT: + if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) { + RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_DESELECT)); + } + break; + case GESTURE_MODAL_IN: + case GESTURE_MODAL_OUT: + if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) { + RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_OUT)); + } + break; + } +} + +static int gesture_modal_state_from_operator(wmOperator *op) +{ + PropertyRNA *prop; + + if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) { + if (RNA_property_is_set(op->ptr, prop)) { + return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_DESELECT : GESTURE_MODAL_SELECT; + } + } + if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) { + if (RNA_property_is_set(op->ptr, prop)) { + return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_OUT : GESTURE_MODAL_IN; + } + } + return GESTURE_MODAL_NOP; +} +/** \} */ + + +/* -------------------------------------------------------------------- */ +/** \name Border Gesture + * + * Border gesture has two types: + * -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border. + * -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends. + * + * It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type). + * + * \{ */ + +static bool gesture_border_apply_rect(wmOperator *op) +{ + wmGesture *gesture = op->customdata; + rcti *rect = gesture->customdata; + + if (rect->xmin == rect->xmax || rect->ymin == rect->ymax) + return 0; + + + /* operator arguments and storage. */ + RNA_int_set(op->ptr, "xmin", min_ii(rect->xmin, rect->xmax)); + RNA_int_set(op->ptr, "ymin", min_ii(rect->ymin, rect->ymax)); + RNA_int_set(op->ptr, "xmax", max_ii(rect->xmin, rect->xmax)); + RNA_int_set(op->ptr, "ymax", max_ii(rect->ymin, rect->ymax)); + + return 1; +} + +static bool gesture_border_apply(bContext *C, wmOperator *op) +{ + wmGesture *gesture = op->customdata; + + int retval; + + if (!gesture_border_apply_rect(op)) { + return 0; + } + + gesture_modal_state_to_operator(op, gesture->modal_state); + + retval = op->type->exec(C, op); + OPERATOR_RETVAL_CHECK(retval); + + return 1; +} + +int WM_gesture_border_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + int modal_state = gesture_modal_state_from_operator(op); + + if (ISTWEAK(event->type) || (modal_state != GESTURE_MODAL_NOP)) { + op->customdata = WM_gesture_new(C, event, WM_GESTURE_RECT); + } + else { + op->customdata = WM_gesture_new(C, event, WM_GESTURE_CROSS_RECT); + } + + /* Starting with the mode starts immediately, like having 'wait_for_input' disabled (some tools use this). */ + if (modal_state == GESTURE_MODAL_NOP) { + wmGesture *gesture = op->customdata; + gesture->wait_for_input = true; + } + else { + wmGesture *gesture = op->customdata; + gesture->modal_state = modal_state; + } + + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + wm_gesture_tag_redraw(C); + + return OPERATOR_RUNNING_MODAL; +} + +int WM_gesture_border_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + wmGesture *gesture = op->customdata; + rcti *rect = gesture->customdata; + int sx, sy; + + if (event->type == MOUSEMOVE) { + wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); + + if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) { + rect->xmin = rect->xmax = event->x - sx; + rect->ymin = rect->ymax = event->y - sy; + } + else { + rect->xmax = event->x - sx; + rect->ymax = event->y - sy; + } + gesture_border_apply_rect(op); + + wm_gesture_tag_redraw(C); + } + else if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case GESTURE_MODAL_BEGIN: + if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) { + gesture->is_active = true; + wm_gesture_tag_redraw(C); + } + break; + case GESTURE_MODAL_SELECT: + case GESTURE_MODAL_DESELECT: + case GESTURE_MODAL_IN: + case GESTURE_MODAL_OUT: + if (gesture->wait_for_input) { + gesture->modal_state = event->val; + } + if (gesture_border_apply(C, op)) { + gesture_modal_end(C, op); + return OPERATOR_FINISHED; + } + gesture_modal_end(C, op); + return OPERATOR_CANCELLED; + + case GESTURE_MODAL_CANCEL: + gesture_modal_end(C, op); + return OPERATOR_CANCELLED; + } + + } +#ifdef WITH_INPUT_NDOF + else if (event->type == NDOF_MOTION) { + return OPERATOR_PASS_THROUGH; + } +#endif + +#if 0 + /* Allow view navigation??? */ + else { + return OPERATOR_PASS_THROUGH; + } +#endif + + return OPERATOR_RUNNING_MODAL; +} + +void WM_gesture_border_cancel(bContext *C, wmOperator *op) +{ + gesture_modal_end(C, op); +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ +/** \name Circle Gesture + * + * Currently only used for selection or modal paint stuff, + * calls ``exec`` while hold mouse, exits on release (with no difference between cancel and confirm). + * + * \{ */ + +static void gesture_circle_apply(bContext *C, wmOperator *op); + +int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + int modal_state = gesture_modal_state_from_operator(op); + + op->customdata = WM_gesture_new(C, event, WM_GESTURE_CIRCLE); + wmGesture *gesture = op->customdata; + rcti *rect = gesture->customdata; + + /* Default or previously stored value. */ + rect->xmax = RNA_int_get(op->ptr, "radius"); + + /* Starting with the mode starts immediately, like having 'wait_for_input' disabled (some tools use this). */ + if (modal_state == GESTURE_MODAL_NOP) { + gesture->wait_for_input = true; + } + else { + gesture->is_active = true; + gesture->modal_state = modal_state; + gesture_circle_apply(C, op); + } + + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + wm_gesture_tag_redraw(C); + + return OPERATOR_RUNNING_MODAL; +} + +static void gesture_circle_apply(bContext *C, wmOperator *op) +{ + wmGesture *gesture = op->customdata; + rcti *rect = gesture->customdata; + + if (gesture->modal_state == GESTURE_MODAL_NOP) { + return; + } + + /* operator arguments and storage. */ + RNA_int_set(op->ptr, "x", rect->xmin); + RNA_int_set(op->ptr, "y", rect->ymin); + RNA_int_set(op->ptr, "radius", rect->xmax); + + gesture_modal_state_to_operator(op, gesture->modal_state); + + if (op->type->exec) { + int retval; + retval = op->type->exec(C, op); + OPERATOR_RETVAL_CHECK(retval); + } +} + +int WM_gesture_circle_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + wmGesture *gesture = op->customdata; + rcti *rect = gesture->customdata; + int sx, sy; + + if (event->type == MOUSEMOVE) { + wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); + + rect->xmin = event->x - sx; + rect->ymin = event->y - sy; + + wm_gesture_tag_redraw(C); + + if (gesture->is_active) { + gesture_circle_apply(C, op); + } + } + else if (event->type == EVT_MODAL_MAP) { + bool is_circle_size = false; + bool is_finished = false; + float fac; + + switch (event->val) { + case GESTURE_MODAL_CIRCLE_SIZE: + fac = 0.3f * (event->y - event->prevy); + if (fac > 0) + rect->xmax += ceil(fac); + else + rect->xmax += floor(fac); + if (rect->xmax < 1) rect->xmax = 1; + is_circle_size = true; + break; + case GESTURE_MODAL_CIRCLE_ADD: + rect->xmax += 2 + rect->xmax / 10; + is_circle_size = true; + break; + case GESTURE_MODAL_CIRCLE_SUB: + rect->xmax -= 2 + rect->xmax / 10; + if (rect->xmax < 1) rect->xmax = 1; + is_circle_size = true; + break; + case GESTURE_MODAL_SELECT: + case GESTURE_MODAL_DESELECT: + case GESTURE_MODAL_NOP: + { + if (gesture->wait_for_input) { + gesture->modal_state = event->val; + } + if (event->val == GESTURE_MODAL_NOP) { + /* Single action, click-drag & release to exit. */ + if (gesture->wait_for_input == false) { + is_finished = true; + } + } + else { + /* apply first click */ + gesture_circle_apply(C, op); + gesture->is_active = true; + wm_gesture_tag_redraw(C); + } + break; + } + case GESTURE_MODAL_CANCEL: + case GESTURE_MODAL_CONFIRM: + is_finished = true; + } + + if (is_finished) { + gesture_modal_end(C, op); + return OPERATOR_FINISHED; /* use finish or we don't get an undo */ + } + + if (is_circle_size) { + wm_gesture_tag_redraw(C); + + /* So next use remembers last seen size, even if we didn't apply it. */ + RNA_int_set(op->ptr, "radius", rect->xmax); + } + } +#ifdef WITH_INPUT_NDOF + else if (event->type == NDOF_MOTION) { + return OPERATOR_PASS_THROUGH; + } +#endif + +#if 0 + /* Allow view navigation??? */ + /* note, this gives issues: + * 1) other modal ops run on top (border select), + * 2) middlemouse is used now 3) tablet/trackpad? */ + else { + return OPERATOR_PASS_THROUGH; + } +#endif + + + return OPERATOR_RUNNING_MODAL; +} + +void WM_gesture_circle_cancel(bContext *C, wmOperator *op) +{ + gesture_modal_end(C, op); +} + +#if 0 +/* template to copy from */ +void WM_OT_circle_gesture(wmOperatorType *ot) +{ + ot->name = "Circle Gesture"; + ot->idname = "WM_OT_circle_gesture"; + ot->description = "Enter rotate mode with a circular gesture"; + + ot->invoke = WM_gesture_circle_invoke; + ot->modal = WM_gesture_circle_modal; + ot->poll = WM_operator_winactive; + + /* properties */ + WM_operator_properties_gesture_circle(ot); + +} +#endif + +/** \} */ + + +/* -------------------------------------------------------------------- */ +/** \name Tweak Gesture + * \{ */ + +static void gesture_tweak_modal(bContext *C, const wmEvent *event) +{ + wmWindow *window = CTX_wm_window(C); + wmGesture *gesture = window->tweak; + rcti *rect = gesture->customdata; + int sx, sy, val; + + switch (event->type) { + case MOUSEMOVE: + case INBETWEEN_MOUSEMOVE: + + wm_subwindow_origin_get(window, gesture->swinid, &sx, &sy); + + rect->xmax = event->x - sx; + rect->ymax = event->y - sy; + + if ((val = wm_gesture_evaluate(gesture))) { + wmEvent tevent; + + wm_event_init_from_window(window, &tevent); + /* We want to get coord from start of drag, not from point where it becomes a tweak event, see T40549 */ + tevent.x = rect->xmin + sx; + tevent.y = rect->ymin + sy; + if (gesture->event_type == LEFTMOUSE) + tevent.type = EVT_TWEAK_L; + else if (gesture->event_type == RIGHTMOUSE) + tevent.type = EVT_TWEAK_R; + else + tevent.type = EVT_TWEAK_M; + tevent.val = val; + /* mouse coords! */ + + /* important we add immediately after this event, so future mouse releases + * (which may be in the queue already), are handled in order, see T44740 */ + wm_event_add_ex(window, &tevent, event); + + WM_gesture_end(C, gesture); /* frees gesture itself, and unregisters from window */ + } + + break; + + case LEFTMOUSE: + case RIGHTMOUSE: + case MIDDLEMOUSE: + if (gesture->event_type == event->type) { + WM_gesture_end(C, gesture); + + /* when tweak fails we should give the other keymap entries a chance */ + + /* XXX, assigning to readonly, BAD JUJU! */ + ((wmEvent *)event)->val = KM_RELEASE; + } + break; + default: + if (!ISTIMER(event->type) && event->type != EVENT_NONE) { + WM_gesture_end(C, gesture); + } + break; + } +} + +/* standard tweak, called after window handlers passed on event */ +void wm_tweakevent_test(bContext *C, const wmEvent *event, int action) +{ + wmWindow *win = CTX_wm_window(C); + + if (win->tweak == NULL) { + if (CTX_wm_region(C)) { + if (event->val == KM_PRESS) { + if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) { + win->tweak = WM_gesture_new(C, event, WM_GESTURE_TWEAK); + } + } + } + } + else { + /* no tweaks if event was handled */ + if ((action & WM_HANDLER_BREAK)) { + WM_gesture_end(C, win->tweak); + } + else + gesture_tweak_modal(C, event); + } +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ +/** \name Lasso Gesture + * \{ */ + +int WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + PropertyRNA *prop; + + op->customdata = WM_gesture_new(C, event, WM_GESTURE_LASSO); + + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + wm_gesture_tag_redraw(C); + + if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { + WM_cursor_modal_set(CTX_wm_window(C), RNA_property_int_get(op->ptr, prop)); + } + + return OPERATOR_RUNNING_MODAL; +} + +int WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + PropertyRNA *prop; + + op->customdata = WM_gesture_new(C, event, WM_GESTURE_LINES); + + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + wm_gesture_tag_redraw(C); + + if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { + WM_cursor_modal_set(CTX_wm_window(C), RNA_property_int_get(op->ptr, prop)); + } + + return OPERATOR_RUNNING_MODAL; +} + + +static void gesture_lasso_apply(bContext *C, wmOperator *op) +{ + wmGesture *gesture = op->customdata; + PointerRNA itemptr; + float loc[2]; + int i; + const short *lasso = gesture->customdata; + + /* operator storage as path. */ + + RNA_collection_clear(op->ptr, "path"); + for (i = 0; i < gesture->points; i++, lasso += 2) { + loc[0] = lasso[0]; + loc[1] = lasso[1]; + RNA_collection_add(op->ptr, "path", &itemptr); + RNA_float_set_array(&itemptr, "loc", loc); + } + + gesture_modal_end(C, op); + + if (op->type->exec) { + int retval = op->type->exec(C, op); + OPERATOR_RETVAL_CHECK(retval); + } +} + +int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + wmGesture *gesture = op->customdata; + int sx, sy; + + switch (event->type) { + case MOUSEMOVE: + case INBETWEEN_MOUSEMOVE: + + wm_gesture_tag_redraw(C); + + wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); + + if (gesture->points == gesture->points_alloc) { + gesture->points_alloc *= 2; + gesture->customdata = MEM_reallocN(gesture->customdata, sizeof(short[2]) * gesture->points_alloc); + } + + { + int x, y; + short *lasso = gesture->customdata; + + lasso += (2 * gesture->points - 2); + x = (event->x - sx - lasso[0]); + y = (event->y - sy - lasso[1]); + + /* make a simple distance check to get a smoother lasso + * add only when at least 2 pixels between this and previous location */ + if ((x * x + y * y) > 4) { + lasso += 2; + lasso[0] = event->x - sx; + lasso[1] = event->y - sy; + gesture->points++; + } + } + break; + + case LEFTMOUSE: + case MIDDLEMOUSE: + case RIGHTMOUSE: + if (event->val == KM_RELEASE) { /* key release */ + gesture_lasso_apply(C, op); + return OPERATOR_FINISHED; + } + break; + case ESCKEY: + gesture_modal_end(C, op); + return OPERATOR_CANCELLED; + } + return OPERATOR_RUNNING_MODAL; +} + +int WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return WM_gesture_lasso_modal(C, op, event); +} + +void WM_gesture_lasso_cancel(bContext *C, wmOperator *op) +{ + gesture_modal_end(C, op); +} + +void WM_gesture_lines_cancel(bContext *C, wmOperator *op) +{ + gesture_modal_end(C, op); +} + +/** + * helper function, we may want to add options for conversion to view space + * + * caller must free. + */ +const int (*WM_gesture_lasso_path_to_array(bContext *UNUSED(C), wmOperator *op, int *mcords_tot))[2] +{ + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "path"); + int (*mcords)[2] = NULL; + BLI_assert(prop != NULL); + + if (prop) { + const int len = RNA_property_collection_length(op->ptr, prop); + + if (len) { + int i = 0; + mcords = MEM_mallocN(sizeof(int) * 2 * len, __func__); + + RNA_PROP_BEGIN (op->ptr, itemptr, prop) + { + float loc[2]; + + RNA_float_get_array(&itemptr, "loc", loc); + mcords[i][0] = (int)loc[0]; + mcords[i][1] = (int)loc[1]; + i++; + } + RNA_PROP_END; + } + *mcords_tot = len; + } + else { + *mcords_tot = 0; + } + + /* cast for 'const' */ + return (const int (*)[2])mcords; +} + +#if 0 +/* template to copy from */ + +static int gesture_lasso_exec(bContext *C, wmOperator *op) +{ + RNA_BEGIN (op->ptr, itemptr, "path") + { + float loc[2]; + + RNA_float_get_array(&itemptr, "loc", loc); + printf("Location: %f %f\n", loc[0], loc[1]); + } + RNA_END; + + return OPERATOR_FINISHED; +} + +void WM_OT_lasso_gesture(wmOperatorType *ot) +{ + PropertyRNA *prop; + + ot->name = "Lasso Gesture"; + ot->idname = "WM_OT_lasso_gesture"; + ot->description = "Select objects within the lasso as you move the pointer"; + + ot->invoke = WM_gesture_lasso_invoke; + ot->modal = WM_gesture_lasso_modal; + ot->exec = gesture_lasso_exec; + + ot->poll = WM_operator_winactive; + + prop = RNA_def_property(ot->srna, "path", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_runtime(prop, &RNA_OperatorMousePath); +} +#endif + + +/** \} */ + + +/* -------------------------------------------------------------------- */ +/** \name Straight Line Gesture + * \{ */ + +static bool gesture_straightline_apply(bContext *C, wmOperator *op) +{ + wmGesture *gesture = op->customdata; + rcti *rect = gesture->customdata; + + if (rect->xmin == rect->xmax && rect->ymin == rect->ymax) + return 0; + + /* operator arguments and storage. */ + RNA_int_set(op->ptr, "xstart", rect->xmin); + RNA_int_set(op->ptr, "ystart", rect->ymin); + RNA_int_set(op->ptr, "xend", rect->xmax); + RNA_int_set(op->ptr, "yend", rect->ymax); + + if (op->type->exec) { + int retval = op->type->exec(C, op); + OPERATOR_RETVAL_CHECK(retval); + } + + return 1; +} + +int WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + PropertyRNA *prop; + + op->customdata = WM_gesture_new(C, event, WM_GESTURE_STRAIGHTLINE); + + if (ISTWEAK(event->type)) { + wmGesture *gesture = op->customdata; + gesture->is_active = true; + } + + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + wm_gesture_tag_redraw(C); + + if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { + WM_cursor_modal_set(CTX_wm_window(C), RNA_property_int_get(op->ptr, prop)); + } + + return OPERATOR_RUNNING_MODAL; +} + +int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + wmGesture *gesture = op->customdata; + rcti *rect = gesture->customdata; + int sx, sy; + + if (event->type == MOUSEMOVE) { + wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); + + if (gesture->is_active == false) { + rect->xmin = rect->xmax = event->x - sx; + rect->ymin = rect->ymax = event->y - sy; + } + else { + rect->xmax = event->x - sx; + rect->ymax = event->y - sy; + gesture_straightline_apply(C, op); + } + + wm_gesture_tag_redraw(C); + } + else if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case GESTURE_MODAL_BEGIN: + if (gesture->is_active == false) { + gesture->is_active = true; + wm_gesture_tag_redraw(C); + } + break; + case GESTURE_MODAL_SELECT: + if (gesture_straightline_apply(C, op)) { + gesture_modal_end(C, op); + return OPERATOR_FINISHED; + } + gesture_modal_end(C, op); + return OPERATOR_CANCELLED; + + case GESTURE_MODAL_CANCEL: + gesture_modal_end(C, op); + return OPERATOR_CANCELLED; + } + } + + return OPERATOR_RUNNING_MODAL; +} + +void WM_gesture_straightline_cancel(bContext *C, wmOperator *op) +{ + gesture_modal_end(C, op); +} + +#if 0 +/* template to copy from */ +void WM_OT_straightline_gesture(wmOperatorType *ot) +{ + PropertyRNA *prop; + + ot->name = "Straight Line Gesture"; + ot->idname = "WM_OT_straightline_gesture"; + ot->description = "Draw a straight line as you move the pointer"; + + ot->invoke = WM_gesture_straightline_invoke; + ot->modal = WM_gesture_straightline_modal; + ot->exec = gesture_straightline_exec; + + ot->poll = WM_operator_winactive; + + WM_operator_properties_gesture_straightline(ot, 0); +} +#endif + +/** \} */ diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index c11c398c616..52682523212 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -40,6 +40,8 @@ #include "MEM_guardedalloc.h" +#include "CLG_log.h" + #include "DNA_genfile.h" #include "DNA_scene_types.h" #include "DNA_userdef_types.h" @@ -53,6 +55,7 @@ #include "BLI_utildefines.h" #include "BLO_writefile.h" +#include "BLO_undofile.h" #include "BKE_blender.h" #include "BKE_blender_undo.h" @@ -110,6 +113,7 @@ #include "ED_space_api.h" #include "ED_screen.h" #include "ED_util.h" +#include "ED_undo.h" #include "UI_interface.h" #include "BLF_api.h" @@ -127,6 +131,11 @@ # include "BKE_subsurf.h" #endif +CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_OPERATORS, "wm.operator"); +CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_HANDLERS, "wm.handler"); +CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_EVENTS, "wm.event"); +CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_KEYMAPS, "wm.keymap"); + static void wm_init_reports(bContext *C) { ReportList *reports = CTX_wm_reports(C); @@ -142,11 +151,6 @@ static void wm_free_reports(bContext *C) BKE_reports_clear(reports); } -static void wm_undo_kill_callback(bContext *C) -{ - WM_jobs_kill_all_except(CTX_wm_manager(C), CTX_wm_screen(C)); -} - bool wm_start_with_console = false; /* used in creator.c */ /* only called once, for startup */ @@ -165,7 +169,7 @@ void WM_init(bContext *C, int argc, const char **argv) WM_menutype_init(); WM_uilisttype_init(); - BKE_undo_callback_wm_kill_jobs_set(wm_undo_kill_callback); + ED_undosys_type_init(); BKE_library_callback_free_window_manager_set(wm_close_and_free); /* library.c */ BKE_library_callback_free_notifier_reference_set(WM_main_remove_notifier_reference); /* library.c */ @@ -181,18 +185,15 @@ void WM_init(bContext *C, int argc, const char **argv) ED_file_init(); /* for fsmenu */ ED_node_init_butfuncs(); - BLF_init(11, U.dpi); /* Please update source/gamengine/GamePlayer/GPG_ghost.cpp if you change this */ + BLF_init(); /* Please update source/gamengine/GamePlayer/GPG_ghost.cpp if you change this */ BLT_lang_init(); - /* Enforce loading the UI for the initial homefile */ - G.fileflags &= ~G_FILE_NO_UI; - /* reports cant be initialized before the wm, * but keep before file reading, since that may report errors */ wm_init_reports(C); /* get the default database, plus a wm */ - wm_homefile_read(C, NULL, G.factory_startup, NULL); + wm_homefile_read(C, NULL, G.factory_startup, false, true, NULL, NULL); BLT_lang_set(NULL); @@ -437,6 +438,29 @@ static void wait_for_console_key(void) } #endif +static int wm_exit_handler(bContext *C, const wmEvent *event, void *userdata) +{ + WM_exit(C); + + UNUSED_VARS(event, userdata); + return WM_UI_HANDLER_BREAK; +} + +/** + * Cause a delayed WM_exit() call to avoid leaking memory when trying to exit from within operators. + */ +void wm_exit_schedule_delayed(const bContext *C) +{ + /* What we do here is a little bit hacky, but quite simple and doesn't require bigger + * changes: Add a handler wrapping WM_exit() to cause a delayed call of it. */ + + wmWindow *win = CTX_wm_window(C); + + /* Use modal UI handler for now. Could add separate WM handlers or so, but probably not worth it. */ + WM_event_add_ui_handler(C, &win->modalhandlers, wm_exit_handler, NULL, NULL, 0); + WM_event_add_mousemove(C); /* ensure handler actually gets called */ +} + /** * \note doesn't run exit() call #WM_exit() for that. */ @@ -444,8 +468,6 @@ void WM_exit_ext(bContext *C, const bool do_python) { wmWindowManager *wm = C ? CTX_wm_manager(C) : NULL; - BKE_sound_exit(); - /* first wrap up running stuff, we assume only the active WM is running */ /* modal handlers are on window level freed, others too? */ /* note; same code copied in wm_files.c */ @@ -453,7 +475,8 @@ void WM_exit_ext(bContext *C, const bool do_python) wmWindow *win; if (!G.background) { - if ((U.uiflag2 & USER_KEEP_SESSION) || BKE_undo_is_valid(NULL)) { + struct MemFile *undo_memfile = wm->undo_stack ? ED_undosys_stack_memfile_get_active(wm->undo_stack) : NULL; + if ((U.uiflag2 & USER_KEEP_SESSION) || (undo_memfile != NULL)) { /* save the undo state as quit.blend */ char filename[FILE_MAX]; bool has_edited; @@ -464,7 +487,7 @@ void WM_exit_ext(bContext *C, const bool do_python) has_edited = ED_editors_flush_edits(C, false); if ((has_edited && BLO_write_file(CTX_data_main(C), filename, fileflags, NULL, NULL)) || - BKE_undo_save_file(filename)) + (undo_memfile && BLO_memfile_write_file(undo_memfile, filename))) { printf("Saved session recovery to '%s'\n", filename); } @@ -487,11 +510,13 @@ void WM_exit_ext(bContext *C, const bool do_python) wm_dropbox_free(); WM_menutype_free(); WM_uilisttype_free(); - + /* all non-screen and non-space stuff editors did, like editmode */ if (C) ED_editors_exit(C); + ED_undosys_type_free(); + // XXX // BIF_GlobalReebFree(); // BIF_freeRetarget(); @@ -569,12 +594,10 @@ void WM_exit_ext(bContext *C, const bool do_python) GPU_exit(); } - BKE_undo_reset(); - ED_file_exit(); /* for fsmenu */ UI_exit(); - BKE_blender_userdef_free(); + BKE_blender_userdef_data_free(&U, false); RNA_exit(); /* should be after BPY_python_end so struct python slots are cleared */ @@ -591,6 +614,12 @@ void WM_exit_ext(bContext *C, const bool do_python) BLI_threadapi_exit(); + /* No need to call this early, rather do it late so that other pieces of Blender using sound may exit cleanly, + * see also T50676. */ + BKE_sound_exit(); + + CLG_exit(); + BKE_blender_atexit(); if (MEM_get_memory_blocks_in_use() != 0) { @@ -605,6 +634,10 @@ void WM_exit_ext(bContext *C, const bool do_python) BKE_tempdir_session_purge(); } +/** + * \brief Main exit function to close Blender ordinarily. + * \note Use #wm_exit_schedule_delayed() to close Blender from an operator. Might leak memory otherwise. + */ void WM_exit(bContext *C) { WM_exit_ext(C, 1); diff --git a/source/blender/windowmanager/intern/wm_jobs.c b/source/blender/windowmanager/intern/wm_jobs.c index 5580d3217a5..f9fe787ed2d 100644 --- a/source/blender/windowmanager/intern/wm_jobs.c +++ b/source/blender/windowmanager/intern/wm_jobs.c @@ -379,7 +379,7 @@ static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test) } } - /* possible suspend ourselfs, waiting for other jobs, or de-suspend */ + /* Possible suspend ourselves, waiting for other jobs, or de-suspend. */ test->suspended = suspend; // if (suspend) printf("job suspended: %s\n", test->name); } @@ -418,8 +418,8 @@ void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job) // printf("job started: %s\n", wm_job->name); - BLI_init_threads(&wm_job->threads, do_job_thread, 1); - BLI_insert_thread(&wm_job->threads, wm_job); + BLI_threadpool_init(&wm_job->threads, do_job_thread, 1); + BLI_threadpool_insert(&wm_job->threads, wm_job); } /* restarted job has timer already */ @@ -450,7 +450,7 @@ static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job) wm_job->stop = true; WM_job_main_thread_lock_release(wm_job); - BLI_end_threads(&wm_job->threads); + BLI_threadpool_end(&wm_job->threads); WM_job_main_thread_lock_acquire(wm_job); if (wm_job->endjob) @@ -601,7 +601,7 @@ void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt) wm_job->running = false; WM_job_main_thread_lock_release(wm_job); - BLI_end_threads(&wm_job->threads); + BLI_threadpool_end(&wm_job->threads); WM_job_main_thread_lock_acquire(wm_job); if (wm_job->endnote) diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index e201fa433d4..bcfc97a1e23 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -1,4 +1,5 @@ /* + * * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or @@ -39,6 +40,7 @@ #include "DNA_windowmanager_types.h" #include "MEM_guardedalloc.h" +#include "CLG_log.h" #include "BLI_blenlib.h" #include "BLI_utildefines.h" @@ -121,6 +123,13 @@ static void wm_keymap_item_properties_update_ot(wmKeyMapItem *kmi) if (ot->srna != kmi->ptr->type) { /* matches wm_keymap_item_properties_set but doesnt alloc new ptr */ WM_operator_properties_create_ptr(kmi->ptr, ot); + /* 'kmi->ptr->data' NULL'd above, keep using existing properties. + * Note: the operators property types may have changed, + * we will need a more comprehensive sanitize function to support this properly. + */ + if (kmi->properties) { + kmi->ptr->data = kmi->properties; + } WM_operator_properties_sanitize(kmi->ptr, 1); } } @@ -391,6 +400,15 @@ bool WM_keymap_remove(wmKeyConfig *keyconf, wmKeyMap *keymap) } } + +bool WM_keymap_poll(bContext *C, wmKeyMap *keymap) +{ + if (keymap->poll != NULL) { + return keymap->poll(C); + } + return true; +} + static void keymap_event_set(wmKeyMapItem *kmi, short type, short val, int modifier, short keymodifier) { kmi->type = type; @@ -778,7 +796,7 @@ wmKeyMap *WM_keymap_find_all(const bContext *C, const char *idname, int spaceid, /* modal maps get linked to a running operator, and filter the keys before sending to modal() callback */ -wmKeyMap *WM_modalkeymap_add(wmKeyConfig *keyconf, const char *idname, EnumPropertyItem *items) +wmKeyMap *WM_modalkeymap_add(wmKeyConfig *keyconf, const char *idname, const EnumPropertyItem *items) { wmKeyMap *km = WM_keymap_find(keyconf, idname, 0, 0); km->flag |= KEYMAP_MODAL; @@ -870,11 +888,13 @@ wmKeyMapItem *WM_modalkeymap_find_propvalue(wmKeyMap *km, const int propvalue) void WM_modalkeymap_assign(wmKeyMap *km, const char *opname) { wmOperatorType *ot = WM_operatortype_find(opname, 0); - - if (ot) + + if (ot) { ot->modalkeymap = km; - else - printf("error: modalkeymap_assign, unknown operator %s\n", opname); + } + else { + CLOG_ERROR(WM_LOG_KEYMAPS, "unknown operator '%s'", opname); + } } static void wm_user_modal_keymap_set_items(wmWindowManager *wm, wmKeyMap *km) @@ -912,7 +932,7 @@ static void wm_user_modal_keymap_set_items(wmWindowManager *wm, wmKeyMap *km) const char *WM_key_event_string(const short type, const bool compact) { - EnumPropertyItem *it; + const EnumPropertyItem *it; const int i = RNA_enum_from_value(rna_enum_event_type_items, (int)type); if (i == -1) { @@ -933,7 +953,7 @@ const char *WM_key_event_string(const short type, const bool compact) int WM_keymap_item_raw_to_string( const short shift, const short ctrl, const short alt, const short oskey, const short keymodifier, const short val, const short type, const bool compact, - const int len, char *r_str) + char *result, const int result_len) { #define ADD_SEP if (p != buf) *p++ = ' '; (void)0 @@ -993,20 +1013,23 @@ int WM_keymap_item_raw_to_string( BLI_assert(p - buf < sizeof(buf)); /* We need utf8 here, otherwise we may 'cut' some unicode chars like arrows... */ - return BLI_strncpy_utf8_rlen(r_str, buf, len); + return BLI_strncpy_utf8_rlen(result, buf, result_len); #undef ADD_SEP } -int WM_keymap_item_to_string(wmKeyMapItem *kmi, const bool compact, const int len, char *r_str) +int WM_keymap_item_to_string( + wmKeyMapItem *kmi, const bool compact, + char *result, const int result_len) { return WM_keymap_item_raw_to_string( - kmi->shift, kmi->ctrl, kmi->alt, kmi->oskey, kmi->keymodifier, kmi->val, kmi->type, - compact, len, r_str); + kmi->shift, kmi->ctrl, kmi->alt, kmi->oskey, kmi->keymodifier, kmi->val, kmi->type, + compact, result, result_len); } int WM_modalkeymap_items_to_string( - wmKeyMap *km, const int propvalue, const bool compact, const int len, char *r_str) + wmKeyMap *km, const int propvalue, const bool compact, + char *result, const int result_len) { int totlen = 0; bool add_sep = false; @@ -1016,17 +1039,17 @@ int WM_modalkeymap_items_to_string( /* Find all shortcuts related to that propvalue! */ for (kmi = WM_modalkeymap_find_propvalue(km, propvalue); - kmi && totlen < (len - 2); + kmi && totlen < (result_len - 2); kmi = wm_modalkeymap_find_propvalue_iter(km, kmi, propvalue)) { if (add_sep) { - r_str[totlen++] = '/'; - r_str[totlen] = '\0'; + result[totlen++] = '/'; + result[totlen] = '\0'; } else { add_sep = true; } - totlen += WM_keymap_item_to_string(kmi, compact, len - totlen, &r_str[totlen]); + totlen += WM_keymap_item_to_string(kmi, compact, &result[totlen], result_len - totlen); } } @@ -1034,25 +1057,26 @@ int WM_modalkeymap_items_to_string( } int WM_modalkeymap_operator_items_to_string( - wmOperatorType *ot, const int propvalue, const bool compact, const int len, char *r_str) + wmOperatorType *ot, const int propvalue, const bool compact, + char *result, const int result_len) { - return WM_modalkeymap_items_to_string(ot->modalkeymap, propvalue, compact, len, r_str); + return WM_modalkeymap_items_to_string(ot->modalkeymap, propvalue, compact, result, result_len); } char *WM_modalkeymap_operator_items_to_string_buf( wmOperatorType *ot, const int propvalue, const bool compact, - const int max_len, int *r_available_len, char **r_str) + const int max_len, int *r_available_len, char **r_result) { - char *ret = *r_str; + char *ret = *r_result; if (*r_available_len > 1) { int used_len = WM_modalkeymap_operator_items_to_string( - ot, propvalue, compact, min_ii(*r_available_len, max_len), ret) + 1; + ot, propvalue, compact, ret, min_ii(*r_available_len, max_len)) + 1; *r_available_len -= used_len; - *r_str += used_len; + *r_result += used_len; if (*r_available_len == 0) { - (*r_str)--; /* So that *str keeps pointing on a valid char, we'll stay on it anyway. */ + (*r_result)--; /* So that *result keeps pointing on a valid char, we'll stay on it anyway. */ } } else { @@ -1076,7 +1100,7 @@ static wmKeyMapItem *wm_keymap_item_find_handlers( for (handler = handlers->first; handler; handler = handler->next) { keymap = WM_keymap_active(wm, handler->keymap); - if (keymap && (!keymap->poll || keymap->poll((bContext *)C))) { + if (keymap && WM_keymap_poll((bContext *)C, keymap)) { for (kmi = keymap->items.first; kmi; kmi = kmi->next) { /* skip disabled keymap items [T38447] */ if (kmi->flag & KMI_INACTIVE) @@ -1119,7 +1143,7 @@ static wmKeyMapItem *wm_keymap_item_find_handlers( if (IDP_EqualsProperties_ex(properties, properties_default, is_strict)) { char kmi_str[128]; - WM_keymap_item_to_string(kmi, false, sizeof(kmi_str), kmi_str); + WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str)); /* Note gievn properties could come from other things than menu entry... */ printf("%s: Some set values in menu entry match default op values, " "this might not be desired!\n", opname); @@ -1269,7 +1293,7 @@ static wmKeyMapItem *wm_keymap_item_find( kmi = wm_keymap_item_find_props(C, opname, opcontext, properties_default, is_strict, is_hotkey, &km); if (kmi) { char kmi_str[128]; - WM_keymap_item_to_string(kmi, false, sizeof(kmi_str), kmi_str); + WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str)); printf("%s: Some set values in keymap entry match default op values, " "this might not be desired!\n", opname); printf("\tkm: '%s', kmi: '%s'\n", km->idname, kmi_str); @@ -1295,13 +1319,14 @@ static wmKeyMapItem *wm_keymap_item_find( char *WM_key_event_operator_string( const bContext *C, const char *opname, int opcontext, - IDProperty *properties, const bool is_strict, int len, char *r_str) + IDProperty *properties, const bool is_strict, + char *result, const int result_len) { wmKeyMapItem *kmi = wm_keymap_item_find(C, opname, opcontext, properties, false, is_strict, NULL); - + if (kmi) { - WM_keymap_item_to_string(kmi, false, len, r_str); - return r_str; + WM_keymap_item_to_string(kmi, false, result, result_len); + return result; } return NULL; @@ -1707,7 +1732,7 @@ wmKeyMap *WM_keymap_guess_opname(const bContext *C, const char *opname) km = WM_keymap_find_all(C, "Mesh", 0, 0); /* some mesh operators are active in object mode too, like add-prim */ - if (km && km->poll && km->poll((bContext *)C) == 0) { + if (km && !WM_keymap_poll((bContext *)C, km)) { km = WM_keymap_find_all(C, "Object Mode", 0, 0); } } @@ -1717,7 +1742,7 @@ wmKeyMap *WM_keymap_guess_opname(const bContext *C, const char *opname) km = WM_keymap_find_all(C, "Curve", 0, 0); /* some curve operators are active in object mode too, like add-prim */ - if (km && km->poll && km->poll((bContext *)C) == 0) { + if (km && !WM_keymap_poll((bContext *)C, km)) { km = WM_keymap_find_all(C, "Object Mode", 0, 0); } } @@ -1745,7 +1770,7 @@ wmKeyMap *WM_keymap_guess_opname(const bContext *C, const char *opname) km = WM_keymap_find_all(C, "Metaball", 0, 0); /* some mball operators are active in object mode too, like add-prim */ - if (km && km->poll && km->poll((bContext *)C) == 0) { + if (km && !WM_keymap_poll((bContext *)C, km)) { km = WM_keymap_find_all(C, "Object Mode", 0, 0); } } @@ -1797,7 +1822,7 @@ wmKeyMap *WM_keymap_guess_opname(const bContext *C, const char *opname) * Mesh keymap is probably not ideal, but best place I could find to put those. */ if (sl->spacetype == SPACE_VIEW3D) { km = WM_keymap_find_all(C, "Mesh", 0, 0); - if (km && km->poll && !km->poll((bContext *)C)) { + if (km && !WM_keymap_poll((bContext *)C, km)) { km = NULL; } } diff --git a/source/blender/windowmanager/intern/wm_operator_props.c b/source/blender/windowmanager/intern/wm_operator_props.c index 18836f34c99..72e4ff1d83a 100644 --- a/source/blender/windowmanager/intern/wm_operator_props.c +++ b/source/blender/windowmanager/intern/wm_operator_props.c @@ -49,7 +49,7 @@ void WM_operator_properties_filesel( { PropertyRNA *prop; - static EnumPropertyItem file_display_items[] = { + static const EnumPropertyItem file_display_items[] = { {FILE_DEFAULTDISPLAY, "DEFAULT", 0, "Default", "Automatically determine display type for files"}, {FILE_SHORTDISPLAY, "LIST_SHORT", ICON_SHORTDISPLAY, "Short List", "Display files as short list"}, {FILE_LONGDISPLAY, "LIST_LONG", ICON_LONGDISPLAY, "Long List", "Display files as a detailed list"}, @@ -135,7 +135,7 @@ static void wm_operator_properties_select_action_ex(wmOperatorType *ot, int defa void WM_operator_properties_select_action(wmOperatorType *ot, int default_action) { - static EnumPropertyItem select_actions[] = { + static const EnumPropertyItem select_actions[] = { {SEL_TOGGLE, "TOGGLE", 0, "Toggle", "Toggle selection for all elements"}, {SEL_SELECT, "SELECT", 0, "Select", "Select all elements"}, {SEL_DESELECT, "DESELECT", 0, "Deselect", "Deselect all elements"}, @@ -151,7 +151,7 @@ void WM_operator_properties_select_action(wmOperatorType *ot, int default_action */ void WM_operator_properties_select_action_simple(wmOperatorType *ot, int default_action) { - static EnumPropertyItem select_actions[] = { + static const EnumPropertyItem select_actions[] = { {SEL_SELECT, "SELECT", 0, "Select", "Select all elements"}, {SEL_DESELECT, "DESELECT", 0, "Deselect", "Deselect all elements"}, {0, NULL, 0, NULL, NULL} @@ -224,30 +224,69 @@ void WM_operator_properties_border_to_rctf(struct wmOperator *op, rctf *rect) BLI_rctf_rcti_copy(rect, &rect_i); } -void WM_operator_properties_gesture_border(wmOperatorType *ot, bool extend) +/** + * Use with #WM_gesture_border_invoke + */ +void WM_operator_properties_gesture_border_ex(wmOperatorType *ot, bool deselect, bool extend) { - RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Gesture Mode", "", INT_MIN, INT_MAX); - WM_operator_properties_border(ot); + if (deselect) { + RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Deselect rather than select items"); + } if (extend) { RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend selection instead of deselecting everything first"); } } -void WM_operator_properties_mouse_select(wmOperatorType *ot) +void WM_operator_properties_gesture_border_select(wmOperatorType *ot) +{ + WM_operator_properties_gesture_border_ex(ot, true, true); +} +void WM_operator_properties_gesture_border(wmOperatorType *ot) +{ + WM_operator_properties_gesture_border_ex(ot, false, false); +} + +void WM_operator_properties_gesture_border_zoom(wmOperatorType *ot) { + WM_operator_properties_border(ot); + PropertyRNA *prop; + prop = RNA_def_boolean(ot->srna, "zoom_out", false, "Zoom Out", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} - prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", - "Extend selection instead of deselecting everything first"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Remove from selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "toggle", false, "Toggle Selection", "Toggle the selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); +/** + * Use with #WM_gesture_lasso_invoke + */ +void WM_operator_properties_gesture_lasso_ex(wmOperatorType *ot, bool deselect, bool extend) +{ + PropertyRNA *prop; + prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + if (deselect) { + RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Deselect rather than select items"); + } + if (extend) { + RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend selection instead of deselecting everything first"); + } } +void WM_operator_properties_gesture_lasso(wmOperatorType *ot) +{ + WM_operator_properties_gesture_lasso_ex(ot, false, false); +} + +void WM_operator_properties_gesture_lasso_select(wmOperatorType *ot) +{ + WM_operator_properties_gesture_lasso_ex(ot, true, true); +} + +/** + * Use with #WM_gesture_straightline_invoke + */ void WM_operator_properties_gesture_straightline(wmOperatorType *ot, int cursor) { PropertyRNA *prop; @@ -269,6 +308,48 @@ void WM_operator_properties_gesture_straightline(wmOperatorType *ot, int cursor) } /** + * Use with #WM_gesture_circle_invoke + */ +void WM_operator_properties_gesture_circle_ex(wmOperatorType *ot, bool deselect) +{ + PropertyRNA *prop; + const int radius_default = 25; + + prop = RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + prop = RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + RNA_def_int(ot->srna, "radius", radius_default, 1, INT_MAX, "Radius", "", 1, INT_MAX); + + if (deselect) { + RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Deselect rather than select items"); + } +} + +void WM_operator_properties_gesture_circle(wmOperatorType *ot) +{ + WM_operator_properties_gesture_circle_ex(ot, false); +} + +void WM_operator_properties_gesture_circle_select(wmOperatorType *ot) +{ + WM_operator_properties_gesture_circle_ex(ot, true); +} + +void WM_operator_properties_mouse_select(wmOperatorType *ot) +{ + PropertyRNA *prop; + + prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", + "Extend selection instead of deselecting everything first"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Remove from selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "toggle", false, "Toggle Selection", "Toggle the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** * \param nth_can_disable: Enable if we want to be able to select no interval at all. */ void WM_operator_properties_checker_interval(wmOperatorType *ot, bool nth_can_disable) diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index cef5e7e4a8e..f46358f83cf 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -46,6 +46,8 @@ #include "MEM_guardedalloc.h" +#include "CLG_log.h" + #include "DNA_ID.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" @@ -58,7 +60,7 @@ #include "PIL_time.h" #include "BLI_blenlib.h" -#include "BLI_dial.h" +#include "BLI_dial_2d.h" #include "BLI_dynstr.h" /*for WM_operator_pystring */ #include "BLI_math.h" #include "BLI_utildefines.h" @@ -94,7 +96,7 @@ #include "ED_numinput.h" #include "ED_screen.h" -#include "ED_util.h" +#include "ED_undo.h" #include "ED_view3d.h" #include "GPU_basic_shader.h" @@ -115,7 +117,6 @@ #include "wm_event_system.h" #include "wm_event_types.h" #include "wm_files.h" -#include "wm_subwindow.h" #include "wm_window.h" static GHash *global_ops_hash = NULL; @@ -140,12 +141,12 @@ wmOperatorType *WM_operatortype_find(const char *idname, bool quiet) } if (!quiet) { - printf("search for unknown operator '%s', '%s'\n", idname_bl, idname); + CLOG_INFO(WM_LOG_OPERATORS, 0, "search for unknown operator '%s', '%s'\n", idname_bl, idname); } } else { if (!quiet) { - printf("search for empty operator\n"); + CLOG_INFO(WM_LOG_OPERATORS, 0, "search for empty operator"); } } @@ -171,13 +172,12 @@ void WM_operatortype_append(void (*opfunc)(wmOperatorType *)) opfunc(ot); if (ot->name == NULL) { - fprintf(stderr, "ERROR: Operator %s has no name property!\n", ot->idname); - ot->name = N_("Dummy Name"); + CLOG_ERROR(WM_LOG_OPERATORS, "Operator '%s' has no name property", ot->idname); } /* XXX All ops should have a description but for now allow them not to. */ RNA_def_struct_ui_text(ot->srna, ot->name, ot->description ? ot->description : UNDOCUMENTED_OPERATOR_TIP); - RNA_def_struct_identifier(ot->srna, ot->idname); + RNA_def_struct_identifier(&BLENDER_RNA, ot->srna, ot->idname); BLI_ghash_insert(global_ops_hash, (void *)ot->idname, ot); } @@ -193,7 +193,7 @@ void WM_operatortype_append_ptr(void (*opfunc)(wmOperatorType *, void *), void * ot->translation_context = BLT_I18NCONTEXT_OPERATOR_DEFAULT; opfunc(ot, userdata); RNA_def_struct_ui_text(ot->srna, ot->name, ot->description ? ot->description : UNDOCUMENTED_OPERATOR_TIP); - RNA_def_struct_identifier(ot->srna, ot->idname); + RNA_def_struct_identifier(&BLENDER_RNA, ot->srna, ot->idname); BLI_ghash_insert(global_ops_hash, (void *)ot->idname, ot); } @@ -256,7 +256,7 @@ static int wm_macro_exec(bContext *C, wmOperator *op) } } else { - printf("%s: '%s' cant exec macro\n", __func__, opm->type->idname); + CLOG_WARN(WM_LOG_OPERATORS, "'%s' cant exec macro", opm->type->idname); } } @@ -301,8 +301,9 @@ static int wm_macro_modal(bContext *C, wmOperator *op, const wmEvent *event) wmOperator *opm = op->opm; int retval = OPERATOR_FINISHED; - if (opm == NULL) - printf("%s: macro error, calling NULL modal()\n", __func__); + if (opm == NULL) { + CLOG_ERROR(WM_LOG_OPERATORS, "macro error, calling NULL modal()"); + } else { retval = opm->type->modal(C, opm, event); OPERATOR_RETVAL_CHECK(retval); @@ -376,7 +377,7 @@ wmOperatorType *WM_operatortype_append_macro(const char *idname, const char *nam const char *i18n_context; if (WM_operatortype_find(idname, true)) { - printf("%s: macro error: operator %s exists\n", __func__, idname); + CLOG_ERROR(WM_LOG_OPERATORS, "operator %s exists, cannot create macro", idname); return NULL; } @@ -398,7 +399,7 @@ wmOperatorType *WM_operatortype_append_macro(const char *idname, const char *nam ot->description = UNDOCUMENTED_OPERATOR_TIP; RNA_def_struct_ui_text(ot->srna, ot->name, ot->description); - RNA_def_struct_identifier(ot->srna, ot->idname); + RNA_def_struct_identifier(&BLENDER_RNA, ot->srna, ot->idname); /* Use i18n context from ext.srna if possible (py operators). */ i18n_context = ot->ext.srna ? RNA_struct_translation_context(ot->ext.srna) : BLT_I18NCONTEXT_OPERATOR_DEFAULT; RNA_def_struct_translation_context(ot->srna, i18n_context); @@ -432,7 +433,7 @@ void WM_operatortype_append_macro_ptr(void (*opfunc)(wmOperatorType *, void *), opfunc(ot, userdata); RNA_def_struct_ui_text(ot->srna, ot->name, ot->description); - RNA_def_struct_identifier(ot->srna, ot->idname); + RNA_def_struct_identifier(&BLENDER_RNA, ot->srna, ot->idname); BLI_ghash_insert(global_ops_hash, (void *)ot->idname, ot); } @@ -573,6 +574,46 @@ void WM_operator_bl_idname(char *to, const char *from) } /** + * Sanity check to ensure #WM_operator_bl_idname won't fail. + * \returns true when there are no problems with \a idname, otherwise report an error. + */ +bool WM_operator_py_idname_ok_or_report(ReportList *reports, const char *classname, const char *idname) +{ + const char *ch = idname; + int dot = 0; + int i; + for (i = 0; *ch; i++, ch++) { + if ((*ch >= 'a' && *ch <= 'z') || (*ch >= '0' && *ch <= '9') || *ch == '_') { + /* pass */ + } + else if (*ch == '.') { + dot++; + } + else { + BKE_reportf(reports, RPT_ERROR, + "Registering operator class: '%s', invalid bl_idname '%s', at position %d", + classname, idname, i); + return false; + } + } + + if (i > (MAX_NAME - 3)) { + BKE_reportf(reports, RPT_ERROR, "Registering operator class: '%s', invalid bl_idname '%s', " + "is too long, maximum length is %d", classname, idname, + MAX_NAME - 3); + return false; + } + + if (dot != 1) { + BKE_reportf(reports, RPT_ERROR, + "Registering operator class: '%s', invalid bl_idname '%s', must contain 1 '.' character", + classname, idname); + return false; + } + return true; +} + +/** * Print a string representation of the operator, with the args that it runs so python can run it again. * * When calling from an existing wmOperator, better to use simple version: @@ -806,9 +847,19 @@ static char *wm_prop_pystring_from_context(bContext *C, PointerRNA *ptr, Propert } \ } (void)0 +#define CTX_TEST_SPACE_TYPE(space_data_type, member_full, dataptr_cmp) \ + { \ + const char *ctx_member_full = member_full; \ + if (space_data->spacetype == space_data_type && ptr->data == dataptr_cmp) { \ + member_id = ctx_member_full; \ + break; \ + } \ + } (void)0 + switch (GS(((ID *)ptr->id.data)->name)) { case ID_SCE: { + CTX_TEST_PTR_DATA_TYPE(C, "active_gpencil_brush", RNA_GPencilBrush, ptr, CTX_data_active_gpencil_brush(C)); CTX_TEST_PTR_ID(C, "scene", ptr->id.data); break; } @@ -843,12 +894,22 @@ static char *wm_prop_pystring_from_context(bContext *C, PointerRNA *ptr, Propert { CTX_TEST_PTR_ID(C, "screen", ptr->id.data); - CTX_TEST_PTR_DATA_TYPE(C, "space_data", RNA_Space, ptr, CTX_wm_space_data(C)); + SpaceLink *space_data = CTX_wm_space_data(C); + + CTX_TEST_PTR_DATA_TYPE(C, "space_data", RNA_Space, ptr, space_data); CTX_TEST_PTR_DATA_TYPE(C, "area", RNA_Area, ptr, CTX_wm_area(C)); CTX_TEST_PTR_DATA_TYPE(C, "region", RNA_Region, ptr, CTX_wm_region(C)); + CTX_TEST_SPACE_TYPE(SPACE_IMAGE, "space_data.uv_editor", space_data); + CTX_TEST_SPACE_TYPE(SPACE_VIEW3D, "space_data.fx_settings", &(CTX_wm_view3d(C)->fx_settings)); + CTX_TEST_SPACE_TYPE(SPACE_NLA, "space_data.dopesheet", CTX_wm_space_nla(C)->ads); + CTX_TEST_SPACE_TYPE(SPACE_IPO, "space_data.dopesheet", CTX_wm_space_graph(C)->ads); + CTX_TEST_SPACE_TYPE(SPACE_ACTION, "space_data.dopesheet", &(CTX_wm_space_action(C)->ads)); + CTX_TEST_SPACE_TYPE(SPACE_FILE, "space_data.params", CTX_wm_space_file(C)->params); break; } + default: + break; } if (member_id) { @@ -860,6 +921,7 @@ static char *wm_prop_pystring_from_context(bContext *C, PointerRNA *ptr, Propert } #undef CTX_TEST_PTR_ID #undef CTX_TEST_PTR_ID_CAST +#undef CTX_TEST_SPACE_TYPE } return ret; @@ -900,7 +962,7 @@ void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot) void WM_operator_properties_create(PointerRNA *ptr, const char *opstring) { - wmOperatorType *ot = WM_operatortype_find(opstring, 0); + wmOperatorType *ot = WM_operatortype_find(opstring, false); if (ot) WM_operator_properties_create_ptr(ptr, ot); @@ -1075,11 +1137,14 @@ int WM_menu_invoke_ex(bContext *C, wmOperator *op, int opcontext) uiLayout *layout; if (prop == NULL) { - printf("%s: %s has no enum property set\n", __func__, op->type->idname); + CLOG_ERROR(WM_LOG_OPERATORS, + "'%s' has no enum property set", + op->type->idname); } else if (RNA_property_type(prop) != PROP_ENUM) { - printf("%s: %s \"%s\" is not an enum property\n", - __func__, op->type->idname, RNA_property_identifier(prop)); + CLOG_ERROR(WM_LOG_OPERATORS, + "'%s', '%s' is not an enum property", + op->type->idname, RNA_property_identifier(prop)); } else if (RNA_property_is_set(op->ptr, prop)) { const int retval = op->type->exec(C, op); @@ -1118,6 +1183,7 @@ static uiBlock *wm_enum_search_menu(bContext *C, ARegion *ar, void *arg_op) block = UI_block_begin(C, ar, "_popup", UI_EMBOSS); UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU); + search[0] = '\0'; #if 0 /* ok, this isn't so easy... */ uiDefBut(block, UI_BTYPE_LABEL, 0, RNA_struct_ui_name(op->type->srna), 10, 10, UI_searchbox_size_x(), UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); #endif @@ -1162,7 +1228,7 @@ int WM_operator_confirm_message_ex(bContext *C, wmOperator *op, pup = UI_popup_menu_begin(C, title, icon); layout = UI_popup_menu_layout(pup); - uiItemFullO_ptr(layout, op->type, message, ICON_NONE, properties, WM_OP_EXEC_REGION_WIN, 0); + uiItemFullO_ptr(layout, op->type, message, ICON_NONE, properties, WM_OP_EXEC_REGION_WIN, 0, NULL); UI_popup_menu_end(C, pup); return OPERATOR_INTERFACE; @@ -1345,13 +1411,13 @@ static uiBlock *wm_block_create_redo(bContext *C, ARegion *ar, void *arg_op) if (op->type->flag & OPTYPE_MACRO) { for (op = op->macro.first; op; op = op->next) { - uiLayoutOperatorButs(C, layout, op, NULL, 'H', UI_LAYOUT_OP_SHOW_TITLE); + uiTemplateOperatorPropertyButs(C, layout, op, NULL, 'H', UI_TEMPLATE_OP_PROPS_SHOW_TITLE); if (op->next) uiItemS(layout); } } else { - uiLayoutOperatorButs(C, layout, op, NULL, 'H', UI_LAYOUT_OP_SHOW_TITLE); + uiTemplateOperatorPropertyButs(C, layout, op, NULL, 'H', UI_TEMPLATE_OP_PROPS_SHOW_TITLE); } UI_block_bounds_set_popup(block, 4, 0, 0); @@ -1372,7 +1438,7 @@ static void dialog_exec_cb(bContext *C, void *arg1, void *arg2) wmOpPopUp *data = arg1; uiBlock *block = arg2; - /* Explicitly set UI_RETURN_OK flag, otherwise the menu might be cancelled + /* Explicitly set UI_RETURN_OK flag, otherwise the menu might be canceled * in case WM_operator_call_ex exits/reloads the current file (T49199). */ UI_popup_menu_retval_set(block, UI_RETURN_OK, true); @@ -1402,20 +1468,6 @@ static void dialog_exec_cb(bContext *C, void *arg1, void *arg2) } } -static void popup_check_cb(bContext *C, void *op_ptr, void *UNUSED(arg)) -{ - wmOperator *op = op_ptr; - if (op->type->check) { - if (op->type->check(C, op)) { - /* check for popup and re-layout buttons */ - ARegion *ar_menu = CTX_wm_menu(C); - if (ar_menu) { - ED_region_tag_refresh_ui(ar_menu); - } - } - } -} - /* Dialogs are popups that require user verification (click OK) before exec */ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *ar, void *userData) { @@ -1434,9 +1486,7 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *ar, void *userData) layout = UI_block_layout(block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, data->width, data->height, 0, style); - UI_block_func_set(block, popup_check_cb, op, NULL); - - uiLayoutOperatorButs(C, layout, op, NULL, 'H', UI_LAYOUT_OP_SHOW_TITLE); + uiTemplateOperatorPropertyButs(C, layout, op, NULL, 'H', UI_TEMPLATE_OP_PROPS_SHOW_TITLE); /* clear so the OK button is left alone */ UI_block_func_set(block, NULL, NULL, NULL); @@ -1474,10 +1524,8 @@ static uiBlock *wm_operator_ui_create(bContext *C, ARegion *ar, void *userData) layout = UI_block_layout(block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, data->width, data->height, 0, style); - UI_block_func_set(block, popup_check_cb, op, NULL); - /* since ui is defined the auto-layout args are not used */ - uiLayoutOperatorButs(C, layout, op, NULL, 'V', 0); + uiTemplateOperatorPropertyButs(C, layout, op, NULL, 'V', 0); UI_block_func_set(block, NULL, NULL, NULL); @@ -1522,7 +1570,7 @@ int WM_operator_ui_popup(bContext *C, wmOperator *op, int width, int height) data->width = width; data->height = height; data->free_op = true; /* if this runs and gets registered we may want not to free it */ - UI_popup_block_ex(C, wm_operator_ui_create, NULL, wm_operator_ui_popup_cancel, data); + UI_popup_block_ex(C, wm_operator_ui_create, NULL, wm_operator_ui_popup_cancel, data, op); return OPERATOR_RUNNING_MODAL; } @@ -1552,7 +1600,7 @@ static int wm_operator_props_popup_ex(bContext *C, wmOperator *op, if (!do_redo || !(U.uiflag & USER_GLOBALUNDO)) return WM_operator_props_dialog_popup(C, op, 15 * UI_UNIT_X, UI_UNIT_Y); - UI_popup_block_ex(C, wm_block_create_redo, NULL, wm_block_redo_cancel_cb, op); + UI_popup_block_ex(C, wm_block_create_redo, NULL, wm_block_redo_cancel_cb, op, op); if (do_call) wm_block_redo_cb(C, op, 0); @@ -1594,7 +1642,7 @@ int WM_operator_props_dialog_popup(bContext *C, wmOperator *op, int width, int h data->free_op = true; /* if this runs and gets registered we may want not to free it */ /* op is not executed until popup OK but is clicked */ - UI_popup_block_ex(C, wm_block_dialog_create, wm_operator_ui_popup_ok, wm_operator_ui_popup_cancel, data); + UI_popup_block_ex(C, wm_block_dialog_create, wm_operator_ui_popup_ok, wm_operator_ui_popup_cancel, data, op); return OPERATOR_RUNNING_MODAL; } @@ -1761,6 +1809,37 @@ static uiBlock *wm_block_create_splash(bContext *C, ARegion *ar, void *UNUSED(ar ibuf = IMB_ibImageFromMemory((unsigned char *)datatoc_splash_png, datatoc_splash_png_size, IB_rect, NULL, "<splash screen>"); } + + /* overwrite splash with template image */ + if (U.app_template[0] != '\0') { + ImBuf *ibuf_template = NULL; + char splash_filepath[FILE_MAX]; + char template_directory[FILE_MAX]; + + if (BKE_appdir_app_template_id_search( + U.app_template, + template_directory, sizeof(template_directory))) + { + BLI_join_dirfile( + splash_filepath, sizeof(splash_filepath), template_directory, + (U.pixelsize == 2) ? "splash_2x.png" : "splash.png"); + ibuf_template = IMB_loadiffname(splash_filepath, IB_rect, NULL); + if (ibuf_template) { + const int x_expect = ibuf->x; + const int y_expect = 230 * (int)U.pixelsize; + /* don't cover the header text */ + if (ibuf_template->x == x_expect && ibuf_template->y == y_expect) { + memcpy(ibuf->rect, ibuf_template->rect, ibuf_template->x * ibuf_template->y * sizeof(char[4])); + } + else { + CLOG_ERROR(WM_LOG_OPERATORS, + "Splash expected %dx%d found %dx%d, ignoring: %s\n", + x_expect, y_expect, ibuf_template->x, ibuf_template->y, splash_filepath); + } + IMB_freeImBuf(ibuf_template); + } + } + } #endif block = UI_block_begin(C, ar, "_popup", UI_EMBOSS); @@ -1786,13 +1865,13 @@ static uiBlock *wm_block_create_splash(bContext *C, ARegion *ar, void *UNUSED(ar if (version_suffix != NULL && version_suffix[0]) { /* placed after the version number in the image, * placing y is tricky to match baseline */ - int x = 260 - (2 * UI_DPI_WINDOW_FAC); - int y = 242 + (4 * UI_DPI_WINDOW_FAC); - int w = 240; + int x = 260 * U.pixelsize - (2 * UI_DPI_FAC); + int y = 242 * U.pixelsize + (4 * UI_DPI_FAC); + int w = 240 * U.pixelsize; /* hack to have text draw 'text_sel' */ UI_block_emboss_set(block, UI_EMBOSS_NONE); - but = uiDefBut(block, UI_BTYPE_LABEL, 0, version_suffix, x * U.pixelsize, y * U.pixelsize, w * U.pixelsize, UI_UNIT_Y, NULL, 0, 0, 0, 0, NULL); + but = uiDefBut(block, UI_BTYPE_LABEL, 0, version_suffix, x, y, w, UI_UNIT_Y, NULL, 0, 0, 0, 0, NULL); /* XXX, set internal flag - UI_SELECT */ UI_but_flag_enable(but, 1); UI_block_emboss_set(block, UI_EMBOSS); @@ -1819,10 +1898,7 @@ static uiBlock *wm_block_create_splash(bContext *C, ARegion *ar, void *UNUSED(ar UI_block_emboss_set(block, UI_EMBOSS); /* show the splash menu (containing interaction presets), using python */ if (mt) { - Menu menu = {NULL}; - menu.layout = layout; - menu.type = mt; - mt->draw(C, &menu); + UI_menutype_draw(C, mt, layout); // wmWindowManager *wm = CTX_wm_manager(C); // uiItemM(layout, C, "USERPREF_MT_keyconfigs", U.keyconfigstr, ICON_NONE); @@ -1849,13 +1925,11 @@ static uiBlock *wm_block_create_splash(bContext *C, ARegion *ar, void *UNUSED(ar "https://docs.blender.org/manual/en/dev/"); uiItemStringO(col, IFACE_("Blender Website"), ICON_URL, "WM_OT_url_open", "url", "http://www.blender.org"); if (STREQ(STRINGIFY(BLENDER_VERSION_CYCLE), "release")) { - BLI_snprintf(url, sizeof(url), "http://www.blender.org/documentation/blender_python_api_%d_%d" - STRINGIFY(BLENDER_VERSION_CHAR) "_release", + BLI_snprintf(url, sizeof(url), "https://docs.blender.org/api/%d.%d"STRINGIFY(BLENDER_VERSION_CHAR), BLENDER_VERSION / 100, BLENDER_VERSION % 100); } else { - BLI_snprintf(url, sizeof(url), "http://www.blender.org/documentation/blender_python_api_%d_%d_%d", - BLENDER_VERSION / 100, BLENDER_VERSION % 100, BLENDER_SUBVERSION); + BLI_snprintf(url, sizeof(url), "https://docs.blender.org/api/master"); } uiItemStringO(col, IFACE_("Python API Reference"), ICON_URL, "WM_OT_url_open", "url", url); uiItemL(col, "", ICON_NONE); @@ -1881,10 +1955,7 @@ static uiBlock *wm_block_create_splash(bContext *C, ARegion *ar, void *UNUSED(ar mt = WM_menutype_find("USERPREF_MT_splash_footer", false); if (mt) { - Menu menu = {NULL}; - menu.layout = uiLayoutColumn(layout, false); - menu.type = mt; - mt->draw(C, &menu); + UI_menutype_draw(C, mt, uiLayoutColumn(layout, false)); } UI_block_bounds_set_centered(block, 0); @@ -2095,22 +2166,29 @@ static void WM_OT_window_fullscreen_toggle(wmOperatorType *ot) ot->poll = WM_operator_winactive; } -static int wm_exit_blender_exec(bContext *C, wmOperator *op) +static int wm_exit_blender_exec(bContext *C, wmOperator *UNUSED(op)) { - WM_operator_free(op); - - WM_exit(C); - + wm_quit_with_optional_confirmation_prompt(C, CTX_wm_window(C)); return OPERATOR_FINISHED; } +static int wm_exit_blender_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (U.uiflag & USER_QUIT_PROMPT) { + return wm_exit_blender_exec(C, op); + } + else { + return WM_operator_confirm(C, op, event); + } +} + static void WM_OT_quit_blender(wmOperatorType *ot) { ot->name = "Quit Blender"; ot->idname = "WM_OT_quit_blender"; ot->description = "Quit Blender"; - ot->invoke = WM_operator_confirm; + ot->invoke = wm_exit_blender_invoke; ot->exec = wm_exit_blender_exec; } @@ -2171,705 +2249,6 @@ void WM_paint_cursor_end(wmWindowManager *wm, void *handle) } } -/* ************ window gesture operator-callback definitions ************** */ -/* - * These are default callbacks for use in operators requiring gesture input - */ - -/* **************** Border gesture *************** */ - -/** - * Border gesture has two types: - * -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border. - * -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends. - * - * It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type) - */ - -static int border_apply_rect(wmOperator *op) -{ - wmGesture *gesture = op->customdata; - rcti *rect = gesture->customdata; - - if (rect->xmin == rect->xmax || rect->ymin == rect->ymax) - return 0; - - - /* operator arguments and storage. */ - RNA_int_set(op->ptr, "xmin", min_ii(rect->xmin, rect->xmax)); - RNA_int_set(op->ptr, "ymin", min_ii(rect->ymin, rect->ymax)); - RNA_int_set(op->ptr, "xmax", max_ii(rect->xmin, rect->xmax)); - RNA_int_set(op->ptr, "ymax", max_ii(rect->ymin, rect->ymax)); - - return 1; -} - -static int border_apply(bContext *C, wmOperator *op, int gesture_mode) -{ - PropertyRNA *prop; - - int retval; - - if (!border_apply_rect(op)) - return 0; - - /* XXX weak; border should be configured for this without reading event types */ - if ((prop = RNA_struct_find_property(op->ptr, "gesture_mode"))) { - RNA_property_int_set(op->ptr, prop, gesture_mode); - } - - retval = op->type->exec(C, op); - OPERATOR_RETVAL_CHECK(retval); - - return 1; -} - -static void wm_gesture_end(bContext *C, wmOperator *op) -{ - wmGesture *gesture = op->customdata; - - WM_gesture_end(C, gesture); /* frees gesture itself, and unregisters from window */ - op->customdata = NULL; - - ED_area_tag_redraw(CTX_wm_area(C)); - - if (RNA_struct_find_property(op->ptr, "cursor")) { - WM_cursor_modal_restore(CTX_wm_window(C)); - } -} - -int WM_border_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - if (ISTWEAK(event->type)) - op->customdata = WM_gesture_new(C, event, WM_GESTURE_RECT); - else - op->customdata = WM_gesture_new(C, event, WM_GESTURE_CROSS_RECT); - - /* add modal handler */ - WM_event_add_modal_handler(C, op); - - wm_gesture_tag_redraw(C); - - return OPERATOR_RUNNING_MODAL; -} - -int WM_border_select_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - wmGesture *gesture = op->customdata; - rcti *rect = gesture->customdata; - int sx, sy; - - if (event->type == MOUSEMOVE) { - wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); - - if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->mode == 0) { - rect->xmin = rect->xmax = event->x - sx; - rect->ymin = rect->ymax = event->y - sy; - } - else { - rect->xmax = event->x - sx; - rect->ymax = event->y - sy; - } - border_apply_rect(op); - - wm_gesture_tag_redraw(C); - } - else if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case GESTURE_MODAL_BEGIN: - if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->mode == 0) { - gesture->mode = 1; - wm_gesture_tag_redraw(C); - } - break; - case GESTURE_MODAL_SELECT: - case GESTURE_MODAL_DESELECT: - case GESTURE_MODAL_IN: - case GESTURE_MODAL_OUT: - if (border_apply(C, op, event->val)) { - wm_gesture_end(C, op); - return OPERATOR_FINISHED; - } - wm_gesture_end(C, op); - return OPERATOR_CANCELLED; - - case GESTURE_MODAL_CANCEL: - wm_gesture_end(C, op); - return OPERATOR_CANCELLED; - } - - } -#ifdef WITH_INPUT_NDOF - else if (event->type == NDOF_MOTION) { - return OPERATOR_PASS_THROUGH; - } -#endif -// /* Allow view navigation??? */ -// else { -// return OPERATOR_PASS_THROUGH; -// } - - return OPERATOR_RUNNING_MODAL; -} - -void WM_border_select_cancel(bContext *C, wmOperator *op) -{ - wm_gesture_end(C, op); -} - -/* **************** circle gesture *************** */ -/* works now only for selection or modal paint stuff, calls exec while hold mouse, exit on release */ - -#ifdef GESTURE_MEMORY -int circle_select_size = 25; /* XXX - need some operator memory thing! */ -#endif - -int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - op->customdata = WM_gesture_new(C, event, WM_GESTURE_CIRCLE); - - /* add modal handler */ - WM_event_add_modal_handler(C, op); - - wm_gesture_tag_redraw(C); - - return OPERATOR_RUNNING_MODAL; -} - -static void gesture_circle_apply(bContext *C, wmOperator *op) -{ - wmGesture *gesture = op->customdata; - rcti *rect = gesture->customdata; - - if (RNA_int_get(op->ptr, "gesture_mode") == GESTURE_MODAL_NOP) - return; - - /* operator arguments and storage. */ - RNA_int_set(op->ptr, "x", rect->xmin); - RNA_int_set(op->ptr, "y", rect->ymin); - RNA_int_set(op->ptr, "radius", rect->xmax); - - if (op->type->exec) { - int retval; - retval = op->type->exec(C, op); - OPERATOR_RETVAL_CHECK(retval); - } -#ifdef GESTURE_MEMORY - circle_select_size = rect->xmax; -#endif -} - -int WM_gesture_circle_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - wmGesture *gesture = op->customdata; - rcti *rect = gesture->customdata; - int sx, sy; - - if (event->type == MOUSEMOVE) { - wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); - - rect->xmin = event->x - sx; - rect->ymin = event->y - sy; - - wm_gesture_tag_redraw(C); - - if (gesture->mode) - gesture_circle_apply(C, op); - } - else if (event->type == EVT_MODAL_MAP) { - float fac; - - switch (event->val) { - case GESTURE_MODAL_CIRCLE_SIZE: - fac = 0.3f * (event->y - event->prevy); - if (fac > 0) - rect->xmax += ceil(fac); - else - rect->xmax += floor(fac); - if (rect->xmax < 1) rect->xmax = 1; - wm_gesture_tag_redraw(C); - break; - case GESTURE_MODAL_CIRCLE_ADD: - rect->xmax += 2 + rect->xmax / 10; - wm_gesture_tag_redraw(C); - break; - case GESTURE_MODAL_CIRCLE_SUB: - rect->xmax -= 2 + rect->xmax / 10; - if (rect->xmax < 1) rect->xmax = 1; - wm_gesture_tag_redraw(C); - break; - case GESTURE_MODAL_SELECT: - case GESTURE_MODAL_DESELECT: - case GESTURE_MODAL_NOP: - if (RNA_struct_find_property(op->ptr, "gesture_mode")) - RNA_int_set(op->ptr, "gesture_mode", event->val); - - if (event->val != GESTURE_MODAL_NOP) { - /* apply first click */ - gesture_circle_apply(C, op); - gesture->mode = 1; - wm_gesture_tag_redraw(C); - } - break; - - case GESTURE_MODAL_CANCEL: - case GESTURE_MODAL_CONFIRM: - wm_gesture_end(C, op); - return OPERATOR_FINISHED; /* use finish or we don't get an undo */ - } - } -#ifdef WITH_INPUT_NDOF - else if (event->type == NDOF_MOTION) { - return OPERATOR_PASS_THROUGH; - } -#endif - /* Allow view navigation??? */ - /* note, this gives issues: 1) other modal ops run on top (border select), 2) middlemouse is used now 3) tablet/trackpad? */ -// else { -// return OPERATOR_PASS_THROUGH; -// } - - return OPERATOR_RUNNING_MODAL; -} - -void WM_gesture_circle_cancel(bContext *C, wmOperator *op) -{ - wm_gesture_end(C, op); -} - -#if 0 -/* template to copy from */ -void WM_OT_circle_gesture(wmOperatorType *ot) -{ - ot->name = "Circle Gesture"; - ot->idname = "WM_OT_circle_gesture"; - ot->description = "Enter rotate mode with a circular gesture"; - - ot->invoke = WM_gesture_circle_invoke; - ot->modal = WM_gesture_circle_modal; - - ot->poll = WM_operator_winactive; - - RNA_def_property(ot->srna, "x", PROP_INT, PROP_NONE); - RNA_def_property(ot->srna, "y", PROP_INT, PROP_NONE); - RNA_def_property(ot->srna, "radius", PROP_INT, PROP_NONE); - -} -#endif - -/* **************** Tweak gesture *************** */ - -static void tweak_gesture_modal(bContext *C, const wmEvent *event) -{ - wmWindow *window = CTX_wm_window(C); - wmGesture *gesture = window->tweak; - rcti *rect = gesture->customdata; - int sx, sy, val; - - switch (event->type) { - case MOUSEMOVE: - case INBETWEEN_MOUSEMOVE: - - wm_subwindow_origin_get(window, gesture->swinid, &sx, &sy); - - rect->xmax = event->x - sx; - rect->ymax = event->y - sy; - - if ((val = wm_gesture_evaluate(gesture))) { - wmEvent tevent; - - wm_event_init_from_window(window, &tevent); - /* We want to get coord from start of drag, not from point where it becomes a tweak event, see T40549 */ - tevent.x = rect->xmin + sx; - tevent.y = rect->ymin + sy; - if (gesture->event_type == LEFTMOUSE) - tevent.type = EVT_TWEAK_L; - else if (gesture->event_type == RIGHTMOUSE) - tevent.type = EVT_TWEAK_R; - else - tevent.type = EVT_TWEAK_M; - tevent.val = val; - /* mouse coords! */ - - /* important we add immediately after this event, so future mouse releases - * (which may be in the queue already), are handled in order, see T44740 */ - wm_event_add_ex(window, &tevent, event); - - WM_gesture_end(C, gesture); /* frees gesture itself, and unregisters from window */ - } - - break; - - case LEFTMOUSE: - case RIGHTMOUSE: - case MIDDLEMOUSE: - if (gesture->event_type == event->type) { - WM_gesture_end(C, gesture); - - /* when tweak fails we should give the other keymap entries a chance */ - - /* XXX, assigning to readonly, BAD JUJU! */ - ((wmEvent *)event)->val = KM_RELEASE; - } - break; - default: - if (!ISTIMER(event->type) && event->type != EVENT_NONE) { - WM_gesture_end(C, gesture); - } - break; - } -} - -/* standard tweak, called after window handlers passed on event */ -void wm_tweakevent_test(bContext *C, wmEvent *event, int action) -{ - wmWindow *win = CTX_wm_window(C); - - if (win->tweak == NULL) { - if (CTX_wm_region(C)) { - if (event->val == KM_PRESS) { - if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) { - win->tweak = WM_gesture_new(C, event, WM_GESTURE_TWEAK); - } - } - } - } - else { - /* no tweaks if event was handled */ - if ((action & WM_HANDLER_BREAK)) { - WM_gesture_end(C, win->tweak); - } - else - tweak_gesture_modal(C, event); - } -} - -/* *********************** lasso gesture ****************** */ - -int WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - PropertyRNA *prop; - - op->customdata = WM_gesture_new(C, event, WM_GESTURE_LASSO); - - /* add modal handler */ - WM_event_add_modal_handler(C, op); - - wm_gesture_tag_redraw(C); - - if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { - WM_cursor_modal_set(CTX_wm_window(C), RNA_property_int_get(op->ptr, prop)); - } - - return OPERATOR_RUNNING_MODAL; -} - -int WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - PropertyRNA *prop; - - op->customdata = WM_gesture_new(C, event, WM_GESTURE_LINES); - - /* add modal handler */ - WM_event_add_modal_handler(C, op); - - wm_gesture_tag_redraw(C); - - if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { - WM_cursor_modal_set(CTX_wm_window(C), RNA_property_int_get(op->ptr, prop)); - } - - return OPERATOR_RUNNING_MODAL; -} - - -static void gesture_lasso_apply(bContext *C, wmOperator *op) -{ - wmGesture *gesture = op->customdata; - PointerRNA itemptr; - float loc[2]; - int i; - const short *lasso = gesture->customdata; - - /* operator storage as path. */ - - RNA_collection_clear(op->ptr, "path"); - for (i = 0; i < gesture->points; i++, lasso += 2) { - loc[0] = lasso[0]; - loc[1] = lasso[1]; - RNA_collection_add(op->ptr, "path", &itemptr); - RNA_float_set_array(&itemptr, "loc", loc); - } - - wm_gesture_end(C, op); - - if (op->type->exec) { - int retval = op->type->exec(C, op); - OPERATOR_RETVAL_CHECK(retval); - } -} - -int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - wmGesture *gesture = op->customdata; - int sx, sy; - - switch (event->type) { - case MOUSEMOVE: - case INBETWEEN_MOUSEMOVE: - - wm_gesture_tag_redraw(C); - - wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); - - if (gesture->points == gesture->size) { - short *old_lasso = gesture->customdata; - gesture->customdata = MEM_callocN(2 * sizeof(short) * (gesture->size + WM_LASSO_MIN_POINTS), "lasso points"); - memcpy(gesture->customdata, old_lasso, 2 * sizeof(short) * gesture->size); - gesture->size = gesture->size + WM_LASSO_MIN_POINTS; - MEM_freeN(old_lasso); - // printf("realloc\n"); - } - - { - int x, y; - short *lasso = gesture->customdata; - - lasso += (2 * gesture->points - 2); - x = (event->x - sx - lasso[0]); - y = (event->y - sy - lasso[1]); - - /* make a simple distance check to get a smoother lasso - * add only when at least 2 pixels between this and previous location */ - if ((x * x + y * y) > 4) { - lasso += 2; - lasso[0] = event->x - sx; - lasso[1] = event->y - sy; - gesture->points++; - } - } - break; - - case LEFTMOUSE: - case MIDDLEMOUSE: - case RIGHTMOUSE: - if (event->val == KM_RELEASE) { /* key release */ - gesture_lasso_apply(C, op); - return OPERATOR_FINISHED; - } - break; - case ESCKEY: - wm_gesture_end(C, op); - return OPERATOR_CANCELLED; - } - return OPERATOR_RUNNING_MODAL; -} - -int WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - return WM_gesture_lasso_modal(C, op, event); -} - -void WM_gesture_lasso_cancel(bContext *C, wmOperator *op) -{ - wm_gesture_end(C, op); -} - -void WM_gesture_lines_cancel(bContext *C, wmOperator *op) -{ - wm_gesture_end(C, op); -} - -/** - * helper function, we may want to add options for conversion to view space - * - * caller must free. - */ -const int (*WM_gesture_lasso_path_to_array(bContext *UNUSED(C), wmOperator *op, int *mcords_tot))[2] -{ - PropertyRNA *prop = RNA_struct_find_property(op->ptr, "path"); - int (*mcords)[2] = NULL; - BLI_assert(prop != NULL); - - if (prop) { - const int len = RNA_property_collection_length(op->ptr, prop); - - if (len) { - int i = 0; - mcords = MEM_mallocN(sizeof(int) * 2 * len, __func__); - - RNA_PROP_BEGIN (op->ptr, itemptr, prop) - { - float loc[2]; - - RNA_float_get_array(&itemptr, "loc", loc); - mcords[i][0] = (int)loc[0]; - mcords[i][1] = (int)loc[1]; - i++; - } - RNA_PROP_END; - } - *mcords_tot = len; - } - else { - *mcords_tot = 0; - } - - /* cast for 'const' */ - return (const int (*)[2])mcords; -} - -#if 0 -/* template to copy from */ - -static int gesture_lasso_exec(bContext *C, wmOperator *op) -{ - RNA_BEGIN (op->ptr, itemptr, "path") - { - float loc[2]; - - RNA_float_get_array(&itemptr, "loc", loc); - printf("Location: %f %f\n", loc[0], loc[1]); - } - RNA_END; - - return OPERATOR_FINISHED; -} - -void WM_OT_lasso_gesture(wmOperatorType *ot) -{ - PropertyRNA *prop; - - ot->name = "Lasso Gesture"; - ot->idname = "WM_OT_lasso_gesture"; - ot->description = "Select objects within the lasso as you move the pointer"; - - ot->invoke = WM_gesture_lasso_invoke; - ot->modal = WM_gesture_lasso_modal; - ot->exec = gesture_lasso_exec; - - ot->poll = WM_operator_winactive; - - prop = RNA_def_property(ot->srna, "path", PROP_COLLECTION, PROP_NONE); - RNA_def_property_struct_runtime(prop, &RNA_OperatorMousePath); -} -#endif - -/* *********************** straight line gesture ****************** */ - -static int straightline_apply(bContext *C, wmOperator *op) -{ - wmGesture *gesture = op->customdata; - rcti *rect = gesture->customdata; - - if (rect->xmin == rect->xmax && rect->ymin == rect->ymax) - return 0; - - /* operator arguments and storage. */ - RNA_int_set(op->ptr, "xstart", rect->xmin); - RNA_int_set(op->ptr, "ystart", rect->ymin); - RNA_int_set(op->ptr, "xend", rect->xmax); - RNA_int_set(op->ptr, "yend", rect->ymax); - - if (op->type->exec) { - int retval = op->type->exec(C, op); - OPERATOR_RETVAL_CHECK(retval); - } - - return 1; -} - - -int WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - PropertyRNA *prop; - - op->customdata = WM_gesture_new(C, event, WM_GESTURE_STRAIGHTLINE); - - /* add modal handler */ - WM_event_add_modal_handler(C, op); - - wm_gesture_tag_redraw(C); - - if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { - WM_cursor_modal_set(CTX_wm_window(C), RNA_property_int_get(op->ptr, prop)); - } - - return OPERATOR_RUNNING_MODAL; -} - -int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - wmGesture *gesture = op->customdata; - rcti *rect = gesture->customdata; - int sx, sy; - - if (event->type == MOUSEMOVE) { - wm_subwindow_origin_get(CTX_wm_window(C), gesture->swinid, &sx, &sy); - - if (gesture->mode == 0) { - rect->xmin = rect->xmax = event->x - sx; - rect->ymin = rect->ymax = event->y - sy; - } - else { - rect->xmax = event->x - sx; - rect->ymax = event->y - sy; - straightline_apply(C, op); - } - - wm_gesture_tag_redraw(C); - } - else if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case GESTURE_MODAL_BEGIN: - if (gesture->mode == 0) { - gesture->mode = 1; - wm_gesture_tag_redraw(C); - } - break; - case GESTURE_MODAL_SELECT: - if (straightline_apply(C, op)) { - wm_gesture_end(C, op); - return OPERATOR_FINISHED; - } - wm_gesture_end(C, op); - return OPERATOR_CANCELLED; - - case GESTURE_MODAL_CANCEL: - wm_gesture_end(C, op); - return OPERATOR_CANCELLED; - } - - } - - return OPERATOR_RUNNING_MODAL; -} - -void WM_gesture_straightline_cancel(bContext *C, wmOperator *op) -{ - wm_gesture_end(C, op); -} - -#if 0 -/* template to copy from */ -void WM_OT_straightline_gesture(wmOperatorType *ot) -{ - PropertyRNA *prop; - - ot->name = "Straight Line Gesture"; - ot->idname = "WM_OT_straightline_gesture"; - ot->description = "Draw a straight line as you move the pointer"; - - ot->invoke = WM_gesture_straightline_invoke; - ot->modal = WM_gesture_straightline_modal; - ot->exec = gesture_straightline_exec; - - ot->poll = WM_operator_winactive; - - WM_operator_properties_gesture_straightline(ot, 0); -} -#endif - /* *********************** radial control ****************** */ #define WM_RADIAL_CONTROL_DISPLAY_SIZE (200 * UI_DPI_FAC) @@ -3661,26 +3040,38 @@ static void WM_OT_radial_control(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; /* all paths relative to the context */ - RNA_def_string(ot->srna, "data_path_primary", NULL, 0, "Primary Data Path", "Primary path of property to be set by the radial control"); + PropertyRNA *prop; + prop = RNA_def_string(ot->srna, "data_path_primary", NULL, 0, "Primary Data Path", "Primary path of property to be set by the radial control"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "data_path_secondary", NULL, 0, "Secondary Data Path", "Secondary path of property to be set by the radial control"); + prop = RNA_def_string(ot->srna, "data_path_secondary", NULL, 0, "Secondary Data Path", "Secondary path of property to be set by the radial control"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "use_secondary", NULL, 0, "Use Secondary", "Path of property to select between the primary and secondary data paths"); + prop = RNA_def_string(ot->srna, "use_secondary", NULL, 0, "Use Secondary", "Path of property to select between the primary and secondary data paths"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "rotation_path", NULL, 0, "Rotation Path", "Path of property used to rotate the texture display"); + prop = RNA_def_string(ot->srna, "rotation_path", NULL, 0, "Rotation Path", "Path of property used to rotate the texture display"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "color_path", NULL, 0, "Color Path", "Path of property used to set the color of the control"); + prop = RNA_def_string(ot->srna, "color_path", NULL, 0, "Color Path", "Path of property used to set the color of the control"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "fill_color_path", NULL, 0, "Fill Color Path", "Path of property used to set the fill color of the control"); + prop = RNA_def_string(ot->srna, "fill_color_path", NULL, 0, "Fill Color Path", "Path of property used to set the fill color of the control"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "fill_color_override_path", NULL, 0, "Fill Color Override Path", ""); - RNA_def_string(ot->srna, "fill_color_override_test_path", NULL, 0, "Fill Color Override Test", ""); + prop = RNA_def_string(ot->srna, "fill_color_override_path", NULL, 0, "Fill Color Override Path", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_string(ot->srna, "fill_color_override_test_path", NULL, 0, "Fill Color Override Test", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "zoom_path", NULL, 0, "Zoom Path", "Path of property used to set the zoom level for the control"); + prop = RNA_def_string(ot->srna, "zoom_path", NULL, 0, "Zoom Path", "Path of property used to set the zoom level for the control"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_string(ot->srna, "image_id", NULL, 0, "Image ID", "Path of ID that is used to generate an image for the control"); + prop = RNA_def_string(ot->srna, "image_id", NULL, 0, "Image ID", "Path of ID that is used to generate an image for the control"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_boolean(ot->srna, "secondary_tex", false, "Secondary Texture", "Tweak brush secondary/mask texture"); + prop = RNA_def_boolean(ot->srna, "secondary_tex", false, "Secondary Texture", "Tweak brush secondary/mask texture"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /* ************************** timer for testing ***************** */ @@ -3710,7 +3101,7 @@ enum { eRTUndo = 6, }; -static EnumPropertyItem redraw_timer_type_items[] = { +static const EnumPropertyItem redraw_timer_type_items[] = { {eRTDrawRegion, "DRAW", 0, "Draw Region", "Draw Region"}, {eRTDrawRegionSwap, "DRAW_SWAP", 0, "Draw Region + Swap", "Draw Region and Swap"}, {eRTDrawWindow, "DRAW_WIN", 0, "Draw Window", "Draw Window"}, @@ -3904,7 +3295,7 @@ static void previews_id_ensure(bContext *C, Scene *scene, ID *id) /* Only preview non-library datablocks, lib ones do not pertain to this .blend file! * Same goes for ID with no user. */ - if (!ID_IS_LINKED_DATABLOCK(id) && (id->us != 0)) { + if (!ID_IS_LINKED(id) && (id->us != 0)) { UI_id_icon_render(C, scene, id, false, false); UI_id_icon_render(C, scene, id, true, false); } @@ -3978,7 +3369,7 @@ static void WM_OT_previews_ensure(wmOperatorType *ot) /* *************************** Datablocks previews clear ************* */ /* Only types supporting previews currently. */ -static EnumPropertyItem preview_id_type_items[] = { +static const EnumPropertyItem preview_id_type_items[] = { {FILTER_ID_SCE, "SCENE", 0, "Scenes", ""}, {FILTER_ID_GR, "GROUP", 0, "Groups", ""}, {FILTER_ID_OB, "OBJECT", 0, "Objects", ""}, @@ -4179,7 +3570,7 @@ void wm_operatortype_init(void) /* circleselect-like modal operators */ static void gesture_circle_modal_keymap(wmKeyConfig *keyconf) { - static EnumPropertyItem modal_items[] = { + static const EnumPropertyItem modal_items[] = { {GESTURE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, {GESTURE_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""}, {GESTURE_MODAL_CIRCLE_ADD, "ADD", 0, "Add", ""}, @@ -4210,14 +3601,15 @@ static void gesture_circle_modal_keymap(wmKeyConfig *keyconf) WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, 0, 0, GESTURE_MODAL_SELECT); + /* Note: use 'KM_ANY' for release, so the circle exits on any mouse release, + * this is needed when circle select is activated as a tool. */ + /* left mouse shift for deselect too */ WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_SHIFT, 0, GESTURE_MODAL_DESELECT); - WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_SHIFT, 0, GESTURE_MODAL_NOP); + WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, GESTURE_MODAL_NOP); WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_PRESS, 0, 0, GESTURE_MODAL_DESELECT); // default 2.4x - WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, 0, 0, GESTURE_MODAL_NOP); // default 2.4x - - WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, 0, 0, GESTURE_MODAL_NOP); + WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, GESTURE_MODAL_NOP); // default 2.4x WM_modalkeymap_add_item(keymap, WHEELUPMOUSE, KM_PRESS, 0, 0, GESTURE_MODAL_CIRCLE_SUB); WM_modalkeymap_add_item(keymap, PADMINUS, KM_PRESS, 0, 0, GESTURE_MODAL_CIRCLE_SUB); @@ -4240,7 +3632,7 @@ static void gesture_circle_modal_keymap(wmKeyConfig *keyconf) /* straight line modal operators */ static void gesture_straightline_modal_keymap(wmKeyConfig *keyconf) { - static EnumPropertyItem modal_items[] = { + static const EnumPropertyItem modal_items[] = { {GESTURE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, {GESTURE_MODAL_SELECT, "SELECT", 0, "Select", ""}, {GESTURE_MODAL_BEGIN, "BEGIN", 0, "Begin", ""}, @@ -4259,7 +3651,7 @@ static void gesture_straightline_modal_keymap(wmKeyConfig *keyconf) WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_ANY, KM_ANY, 0, GESTURE_MODAL_CANCEL); WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, 0, 0, GESTURE_MODAL_BEGIN); - WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, 0, 0, GESTURE_MODAL_SELECT); + WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, GESTURE_MODAL_SELECT); /* assign map to operators */ WM_modalkeymap_assign(keymap, "IMAGE_OT_sample_line"); @@ -4271,7 +3663,7 @@ static void gesture_straightline_modal_keymap(wmKeyConfig *keyconf) /* borderselect-like modal operators */ static void gesture_border_modal_keymap(wmKeyConfig *keyconf) { - static EnumPropertyItem modal_items[] = { + static const EnumPropertyItem modal_items[] = { {GESTURE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, {GESTURE_MODAL_SELECT, "SELECT", 0, "Select", ""}, {GESTURE_MODAL_DESELECT, "DESELECT", 0, "DeSelect", ""}, @@ -4337,7 +3729,7 @@ static void gesture_border_modal_keymap(wmKeyConfig *keyconf) /* zoom to border modal operators */ static void gesture_zoom_border_modal_keymap(wmKeyConfig *keyconf) { - static EnumPropertyItem modal_items[] = { + static const EnumPropertyItem modal_items[] = { {GESTURE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, {GESTURE_MODAL_IN, "IN", 0, "In", ""}, {GESTURE_MODAL_OUT, "OUT", 0, "Out", ""}, @@ -4485,15 +3877,31 @@ void wm_window_keymap(wmKeyConfig *keyconf) gesture_straightline_modal_keymap(keyconf); } +/** + * Filter functions that can be used with rna_id_itemf() below. + * Should return false if 'id' should be excluded. + */ +static bool rna_id_enum_filter_single(ID *id, void *user_data) +{ + return (id != user_data); +} + /* Generic itemf's for operators that take library args */ -static EnumPropertyItem *rna_id_itemf(bContext *UNUSED(C), PointerRNA *UNUSED(ptr), bool *r_free, ID *id, bool local) +static const EnumPropertyItem *rna_id_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), + bool *r_free, ID *id, bool local, + bool (*filter_ids)(ID *id, void *user_data), void *user_data) { EnumPropertyItem item_tmp = {0}, *item = NULL; int totitem = 0; int i = 0; for (; id; id = id->next) { - if (local == false || !ID_IS_LINKED_DATABLOCK(id)) { + if ((filter_ids != NULL) && filter_ids(user_data, id) == false) { + i++; + continue; + } + if (local == false || !ID_IS_LINKED(id)) { item_tmp.identifier = item_tmp.name = id->name + 2; item_tmp.value = i++; RNA_enum_item_add(&item, &totitem, &item_tmp); @@ -4507,58 +3915,64 @@ static EnumPropertyItem *rna_id_itemf(bContext *UNUSED(C), PointerRNA *UNUSED(pt } /* can add more as needed */ -EnumPropertyItem *RNA_action_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_action_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->action.first : NULL, false); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->action.first : NULL, false, NULL, NULL); } #if 0 /* UNUSED */ -EnumPropertyItem *RNA_action_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_action_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->action.first : NULL, true); } #endif -EnumPropertyItem *RNA_group_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_group_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->group.first : NULL, false); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->group.first : NULL, false, NULL, NULL); } -EnumPropertyItem *RNA_group_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_group_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->group.first : NULL, true); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->group.first : NULL, true, NULL, NULL); } -EnumPropertyItem *RNA_image_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_image_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->image.first : NULL, false); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->image.first : NULL, false, NULL, NULL); } -EnumPropertyItem *RNA_image_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_image_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->image.first : NULL, true); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->image.first : NULL, true, NULL, NULL); } -EnumPropertyItem *RNA_scene_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_scene_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->scene.first : NULL, false); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->scene.first : NULL, false, NULL, NULL); } -EnumPropertyItem *RNA_scene_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_scene_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->scene.first : NULL, true); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->scene.first : NULL, true, NULL, NULL); } - -EnumPropertyItem *RNA_movieclip_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_scene_without_active_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +{ + Scene *scene_active = C ? CTX_data_scene(C) : NULL; + return rna_id_itemf( + C, ptr, r_free, C ? (ID *)CTX_data_main(C)->scene.first : NULL, true, + rna_id_enum_filter_single, scene_active); +} +const EnumPropertyItem *RNA_movieclip_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->movieclip.first : NULL, false); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->movieclip.first : NULL, false, NULL, NULL); } -EnumPropertyItem *RNA_movieclip_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_movieclip_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->movieclip.first : NULL, true); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->movieclip.first : NULL, true, NULL, NULL); } -EnumPropertyItem *RNA_mask_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_mask_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->mask.first : NULL, false); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->mask.first : NULL, false, NULL, NULL); } -EnumPropertyItem *RNA_mask_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) +const EnumPropertyItem *RNA_mask_local_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->mask.first : NULL, true); + return rna_id_itemf(C, ptr, r_free, C ? (ID *)CTX_data_main(C)->mask.first : NULL, true, NULL, NULL); } diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c index 6bf7bcc2934..77378cf8e0c 100644 --- a/source/blender/windowmanager/intern/wm_playanim.c +++ b/source/blender/windowmanager/intern/wm_playanim.c @@ -1256,7 +1256,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) //GHOST_ActivateWindowDrawingContext(g_WS.ghost_window); /* initialize the font */ - BLF_init(11, 72); + BLF_init(); ps.fontid = BLF_load_mem("monospace", (unsigned char *)datatoc_bmonofont_ttf, datatoc_bmonofont_ttf_size); BLF_size(ps.fontid, 11, 72); @@ -1428,8 +1428,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.next_frame = ps.direction; - - while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, 0)) || ps.wait2) { + while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, ps.wait2))) { if (hasevent) { GHOST_DispatchEvents(g_WS.ghost_system); } diff --git a/source/blender/windowmanager/intern/wm_stereo.c b/source/blender/windowmanager/intern/wm_stereo.c index 46cee907991..55fe2ec846c 100644 --- a/source/blender/windowmanager/intern/wm_stereo.c +++ b/source/blender/windowmanager/intern/wm_stereo.c @@ -345,6 +345,32 @@ bool WM_stereo3d_enabled(wmWindow *win, bool skip_stereo3d_check) return true; } +/** + * If needed, this adjusts \a r_mouse_xy so that drawn cursor and handled mouse position are matching visually. + */ +void wm_stereo3d_mouse_offset_apply(wmWindow *win, int *r_mouse_xy) +{ + if (!WM_stereo3d_enabled(win, false)) + return; + + if (win->stereo3d_format->display_mode == S3D_DISPLAY_SIDEBYSIDE) { + const int half_x = win->sizex / 2; + /* right half of the screen */ + if (r_mouse_xy[0] > half_x) { + r_mouse_xy[0] -= half_x; + } + r_mouse_xy[0] *= 2; + } + else if (win->stereo3d_format->display_mode == S3D_DISPLAY_TOPBOTTOM) { + const int half_y = win->sizey / 2; + /* upper half of the screen */ + if (r_mouse_xy[1] > half_y) { + r_mouse_xy[1] -= half_y; + } + r_mouse_xy[1] *= 2; + } +} + /************************** Stereo 3D operator **********************************/ typedef struct Stereo3dData { Stereo3dFormat stereo3d_format; diff --git a/source/blender/windowmanager/intern/wm_tooltip.c b/source/blender/windowmanager/intern/wm_tooltip.c new file mode 100644 index 00000000000..86ca95ef377 --- /dev/null +++ b/source/blender/windowmanager/intern/wm_tooltip.c @@ -0,0 +1,106 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/intern/wm_tooltip.c + * \ingroup wm + * + * Manages a per-window tool-tip. + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" + +#include "BKE_context.h" + +#include "ED_screen.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +void WM_tooltip_timer_init( + bContext *C, wmWindow *win, ARegion *ar, + wmTooltipInitFn init) +{ + bScreen *screen = win->screen; + wmWindowManager *wm = CTX_wm_manager(C); + if (screen->tool_tip == NULL) { + screen->tool_tip = MEM_callocN(sizeof(*screen->tool_tip), __func__); + } + screen->tool_tip->region_from = ar; + screen->tool_tip->timer = WM_event_add_timer(wm, win, TIMER, UI_TOOLTIP_DELAY); + screen->tool_tip->init = init; +} + +void WM_tooltip_timer_clear(bContext *C, wmWindow *win) +{ + wmWindowManager *wm = CTX_wm_manager(C); + bScreen *screen = win->screen; + if (screen->tool_tip != NULL) { + if (screen->tool_tip->timer != NULL) { + WM_event_remove_timer(wm, win, screen->tool_tip->timer); + screen->tool_tip->timer = NULL; + } + } +} + +void WM_tooltip_clear(bContext *C, wmWindow *win) +{ + WM_tooltip_timer_clear(C, win); + bScreen *screen = win->screen; + if (screen->tool_tip != NULL) { + if (screen->tool_tip->region) { + UI_tooltip_free(C, screen, screen->tool_tip->region); + screen->tool_tip->region = NULL; + } + MEM_freeN(screen->tool_tip); + screen->tool_tip = NULL; + } +} + +void WM_tooltip_init(bContext *C, wmWindow *win) +{ + WM_tooltip_timer_clear(C, win); + bScreen *screen = win->screen; + if (screen->tool_tip->region) { + UI_tooltip_free(C, screen, screen->tool_tip->region); + screen->tool_tip->region = NULL; + } + screen->tool_tip->region = screen->tool_tip->init( + C, screen->tool_tip->region_from, &screen->tool_tip->exit_on_event); + if (screen->tool_tip->region == NULL) { + WM_tooltip_clear(C, win); + } +} + +void WM_tooltip_refresh(bContext *C, wmWindow *win) +{ + WM_tooltip_timer_clear(C, win); + bScreen *screen = win->screen; + if (screen->tool_tip != NULL) { + if (screen->tool_tip->region) { + UI_tooltip_free(C, screen, screen->tool_tip->region); + screen->tool_tip->region = NULL; + } + WM_tooltip_init(C, win); + } +} diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 2d43c47679d..06bd60e8692 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -70,6 +70,7 @@ #include "ED_fileselect.h" #include "UI_interface.h" +#include "UI_resources.h" #include "PIL_time.h" @@ -77,6 +78,7 @@ #include "GPU_extensions.h" #include "GPU_init_exit.h" #include "GPU_glew.h" +#include "BLF_api.h" /* for assert */ #ifndef NDEBUG @@ -294,11 +296,158 @@ wmWindow *wm_window_copy_test(bContext *C, wmWindow *win_src) } } + +/* -------------------------------------------------------------------- */ +/** \name Quit Confirmation Dialog + * \{ */ + +/** Cancel quitting and close the dialog */ +static void wm_block_confirm_quit_cancel(bContext *C, void *arg_block, void *UNUSED(arg)) +{ + wmWindow *win = CTX_wm_window(C); + UI_popup_block_close(C, win, arg_block); +} + +/** Discard the file changes and quit */ +static void wm_block_confirm_quit_discard(bContext *C, void *arg_block, void *UNUSED(arg)) +{ + wmWindow *win = CTX_wm_window(C); + UI_popup_block_close(C, win, arg_block); + WM_exit(C); +} + +/* Save changes and quit */ +static void wm_block_confirm_quit_save(bContext *C, void *arg_block, void *UNUSED(arg)) +{ + PointerRNA props_ptr; + wmWindow *win = CTX_wm_window(C); + + UI_popup_block_close(C, win, arg_block); + + wmOperatorType *ot = WM_operatortype_find("WM_OT_save_mainfile", false); + + WM_operator_properties_create_ptr(&props_ptr, ot); + RNA_boolean_set(&props_ptr, "exit", true); + /* No need for second confirmation popup. */ + RNA_boolean_set(&props_ptr, "check_existing", false); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr); + WM_operator_properties_free(&props_ptr); +} + + +/* Build the confirm dialog UI */ +static uiBlock *block_create_confirm_quit(struct bContext *C, struct ARegion *ar, void *UNUSED(arg1)) +{ + + uiStyle *style = UI_style_get(); + uiBlock *block = UI_block_begin(C, ar, "confirm_quit_popup", UI_EMBOSS); + + UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT); + UI_block_emboss_set(block, UI_EMBOSS); + + uiLayout *layout = UI_block_layout( + block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 10, 2, U.widget_unit * 24, U.widget_unit * 6, 0, style); + + /* Text and some vertical space */ + { + char *message; + if (G.main->name[0] == '\0') { + message = BLI_strdup(IFACE_("This file has not been saved yet. Save before closing?")); + } + else { + const char *basename = BLI_path_basename(G.main->name); + message = BLI_sprintfN(IFACE_("Save changes to \"%s\" before closing?"), basename); + } + uiItemL(layout, message, ICON_ERROR); + MEM_freeN(message); + } + + uiItemS(layout); + uiItemS(layout); + + + /* Buttons */ + uiBut *but; + + uiLayout *split = uiLayoutSplit(layout, 0.0f, true); + + uiLayout *col = uiLayoutColumn(split, false); + + but = uiDefIconTextBut( + block, UI_BTYPE_BUT, 0, ICON_SCREEN_BACK, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y, + NULL, 0, 0, 0, 0, TIP_("Do not quit")); + UI_but_func_set(but, wm_block_confirm_quit_cancel, block, NULL); + + /* empty space between buttons */ + col = uiLayoutColumn(split, false); + uiItemS(col); + + col = uiLayoutColumn(split, 1); + but = uiDefIconTextBut( + block, UI_BTYPE_BUT, 0, ICON_CANCEL, IFACE_("Discard Changes"), 0, 0, 50, UI_UNIT_Y, + NULL, 0, 0, 0, 0, TIP_("Discard changes and quit")); + UI_but_func_set(but, wm_block_confirm_quit_discard, block, NULL); + + col = uiLayoutColumn(split, 1); + but = uiDefIconTextBut( + block, UI_BTYPE_BUT, 0, ICON_FILE_TICK, IFACE_("Save & Quit"), 0, 0, 50, UI_UNIT_Y, + NULL, 0, 0, 0, 0, TIP_("Save and quit")); + UI_but_func_set(but, wm_block_confirm_quit_save, block, NULL); + + UI_block_bounds_set_centered(block, 10); + + return block; +} + + +/** + * Call the confirm dialog on quitting. It's displayed in the context window so + * caller should set it as desired. + */ +static void wm_confirm_quit(bContext *C) +{ + wmWindow *win = CTX_wm_window(C); + + if (GHOST_SupportsNativeDialogs() == 0) { + UI_popup_block_invoke(C, block_create_confirm_quit, NULL); + } + else if (GHOST_confirmQuit(win->ghostwin)) { + wm_exit_schedule_delayed(C); + } +} + +/** + * Call the quit confirmation prompt or exit directly if needed. The use can + * still cancel via the confirmation popup. Also, this may not quit Blender + * immediately, but rather schedule the closing. + * + * \param win The window to show the confirmation popup/window in. + */ +void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win_ctx = CTX_wm_window(C); + + /* The popup will be displayed in the context window which may not be set + * here (this function gets called outside of normal event handling loop). */ + CTX_wm_window_set(C, win); + + if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved && !G.background) { + wm_confirm_quit(C); + } + else { + wm_exit_schedule_delayed(C); + } + + CTX_wm_window_set(C, win_ctx); +} + +/** \} */ + /* this is event from ghost, or exit-blender op */ void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win) { wmWindow *tmpwin; - bool do_exit = false; /* first check if we have to quit (there are non-temp remaining windows) */ for (tmpwin = wm->windows.first; tmpwin; tmpwin = tmpwin->next) { @@ -308,21 +457,11 @@ void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win) break; } - if (tmpwin == NULL) - do_exit = 1; - - if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved) { - if (do_exit) { - if (!GHOST_confirmQuit(win->ghostwin)) - return; - } - } - - /* let WM_exit do all freeing, for correct quit.blend save */ - if (do_exit) { - WM_exit(C); + if (tmpwin == NULL) { + wm_quit_with_optional_confirmation_prompt(C, win); } else { + /* We're just closing a window */ bScreen *screen = win->screen; BLI_remlink(&wm->windows, win); @@ -374,14 +513,49 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win) } } -static float wm_window_get_virtual_pixelsize(void) +void WM_window_set_dpi(wmWindow *win) { - return ((U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1.0f : 2.0f); -} + float auto_dpi = GHOST_GetDPIHint(win->ghostwin); -float wm_window_pixelsize(wmWindow *win) -{ - return (GHOST_GetNativePixelSize(win->ghostwin) * wm_window_get_virtual_pixelsize()); + /* Clamp auto DPI to 96, since our font/interface drawing does not work well + * with lower sizes. The main case we are interested in supporting is higher + * DPI. If a smaller UI is desired it is still possible to adjust UI scale. */ + auto_dpi = max_ff(auto_dpi, 96.0f); + + /* Lazily init UI scale size, preserving backwards compatibility by + * computing UI scale from ratio of previous DPI and auto DPI */ + if (U.ui_scale == 0) { + int virtual_pixel = (U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1 : 2; + + if (U.dpi == 0) { + U.ui_scale = virtual_pixel; + } + else { + U.ui_scale = (virtual_pixel * U.dpi * 96.0f) / (auto_dpi * 72.0f); + } + + CLAMP(U.ui_scale, 0.25f, 4.0f); + } + + /* Blender's UI drawing assumes DPI 72 as a good default following macOS + * while Windows and Linux use DPI 96. GHOST assumes a default 96 so we + * remap the DPI to Blender's convention. */ + auto_dpi *= GHOST_GetNativePixelSize(win->ghostwin); + int dpi = auto_dpi * U.ui_scale * (72.0 / 96.0f); + + /* Automatically set larger pixel size for high DPI. */ + int pixelsize = max_ii(1, (int)(dpi / 64)); + /* User adjustment for pixel size. */ + pixelsize = max_ii(1, pixelsize + U.ui_line_width); + + /* Set user preferences globals for drawing, and for forward compatibility. */ + U.pixelsize = pixelsize; + U.dpi = dpi / pixelsize; + U.virtual_pixel = (pixelsize == 1) ? VIRTUAL_PIXEL_NATIVE : VIRTUAL_PIXEL_DOUBLE; + U.widget_unit = (U.pixelsize * U.dpi * 20 + 36) / 72; + + /* update font drawing */ + BLF_default_dpi(U.pixelsize * U.dpi); } /* belongs to below */ @@ -412,12 +586,7 @@ static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wm ghostwin = GHOST_CreateWindow(g_system, title, win->posx, posy, win->sizex, win->sizey, -#ifdef __APPLE__ - /* we agreed to not set any fullscreen or iconized state on startup */ - GHOST_kWindowStateNormal, -#else (GHOST_TWindowState)win->windowstate, -#endif GHOST_kDrawingContextTypeOpenGL, glSettings); @@ -441,8 +610,12 @@ static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wm /* store actual window size in blender window */ bounds = GHOST_GetClientBounds(win->ghostwin); - win->sizex = GHOST_GetWidthRectangle(bounds); - win->sizey = GHOST_GetHeightRectangle(bounds); + + /* win32: gives undefined window size when minimized */ + if (GHOST_GetWindowState(win->ghostwin) != GHOST_kWindowStateMinimized) { + win->sizex = GHOST_GetWidthRectangle(bounds); + win->sizey = GHOST_GetHeightRectangle(bounds); + } GHOST_DisposeRectangle(bounds); #ifndef __APPLE__ @@ -456,10 +629,8 @@ static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wm glClear(GL_COLOR_BUFFER_BIT); } - /* displays with larger native pixels, like Macbook. Used to scale dpi with */ /* needed here, because it's used before it reads userdef */ - U.pixelsize = wm_window_pixelsize(win); - BKE_blender_userdef_refresh(); + WM_window_set_dpi(win); wm_window_swap_buffers(win); @@ -472,7 +643,7 @@ static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wm } /** - * Initialize #wmWindows without ghostwin, open these and clear. + * Initialize #wmWindow without ghostwin, open these and clear. * * window size is read from window, if 0 it uses prefsize * called in #WM_check, also inits stuff after file read. @@ -618,15 +789,27 @@ wmWindow *WM_window_open(bContext *C, const rcti *rect) * \param type: WM_WINDOW_RENDER, WM_WINDOW_USERPREFS... * \return the window or NULL. */ -wmWindow *WM_window_open_temp(bContext *C, const rcti *rect_init, int type) +wmWindow *WM_window_open_temp(bContext *C, int x, int y, int sizex, int sizey, int type) { wmWindow *win_prev = CTX_wm_window(C); wmWindow *win; ScrArea *sa; Scene *scene = CTX_data_scene(C); const char *title; - rcti rect = *rect_init; - const short px_virtual = (short)wm_window_get_virtual_pixelsize(); + + /* convert to native OS window coordinates */ + const float native_pixel_size = GHOST_GetNativePixelSize(win_prev->ghostwin); + x /= native_pixel_size; + y /= native_pixel_size; + sizex /= native_pixel_size; + sizey /= native_pixel_size; + + /* calculate postition */ + rcti rect; + rect.xmin = x + win_prev->posx - sizex / 2; + rect.ymin = y + win_prev->posy - sizey / 2; + rect.xmax = rect.xmin + sizex; + rect.ymax = rect.ymin + sizey; /* changes rect to fit within desktop */ wm_window_check_position(&rect); @@ -644,9 +827,8 @@ wmWindow *WM_window_open_temp(bContext *C, const rcti *rect_init, int type) win->posy = rect.ymin; } - /* multiply with virtual pixelsize, ghost handles native one (e.g. for retina) */ - win->sizex = BLI_rcti_size_x(&rect) * px_virtual; - win->sizey = BLI_rcti_size_y(&rect) * px_virtual; + win->sizex = BLI_rcti_size_x(&rect); + win->sizey = BLI_rcti_size_y(&rect); if (win->ghostwin) { wm_window_set_size(win, win->sizex, win->sizey); @@ -835,8 +1017,7 @@ void wm_window_make_drawable(wmWindowManager *wm, wmWindow *win) GHOST_ActivateWindowDrawingContext(win->ghostwin); /* this can change per window */ - U.pixelsize = wm_window_pixelsize(win); - BKE_blender_userdef_refresh(); + WM_window_set_dpi(win); } } @@ -1035,6 +1216,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr if (type == GHOST_kEventWindowSize) { WM_jobs_stop(wm, win->screen, NULL); } + + WM_window_set_dpi(win); /* win32: gives undefined window size when minimized */ if (state != GHOST_kWindowStateMinimized) { @@ -1118,7 +1301,18 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr } break; } - + + case GHOST_kEventWindowDPIHintChanged: + { + WM_window_set_dpi(win); + /* font's are stored at each DPI level, without this we can easy load 100's of fonts */ + BLF_cache_clear(); + + WM_main_add_notifier(NC_WINDOW, NULL); /* full redraw */ + WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL); /* refresh region sizes */ + break; + } + case GHOST_kEventOpenMainFile: { PointerRNA props_ptr; @@ -1199,11 +1393,9 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr { // only update if the actual pixel size changes float prev_pixelsize = U.pixelsize; - U.pixelsize = wm_window_pixelsize(win); + WM_window_set_dpi(win); if (U.pixelsize != prev_pixelsize) { - BKE_blender_userdef_refresh(); - // close all popups since they are positioned with the pixel // size baked in and it's difficult to correct them wmWindow *oldWindow = CTX_wm_window(C); @@ -1407,6 +1599,7 @@ wmTimer *WM_event_add_timer_notifier(wmWindowManager *wm, wmWindow *win, unsigne wt->timestep = timestep; wt->win = win; wt->customdata = SET_UINT_IN_POINTER(type); + wt->flags |= WM_TIMER_NO_FREE_CUSTOM_DATA; BLI_addtail(&wm->timers, wt); @@ -1428,8 +1621,9 @@ void WM_event_remove_timer(wmWindowManager *wm, wmWindow *UNUSED(win), wmTimer * wm->reports.reporttimer = NULL; BLI_remlink(&wm->timers, wt); - if (wt->customdata) + if (wt->customdata != NULL && (wt->flags & WM_TIMER_NO_FREE_CUSTOM_DATA) == 0) { MEM_freeN(wt->customdata); + } MEM_freeN(wt); /* there might be events in queue with this timer as customdata */ |