1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup edscr
22  */
23 
24 #include <math.h>
25 #include <string.h>
26 
27 #include "MEM_guardedalloc.h"
28 
29 #include "BLI_blenlib.h"
30 #include "BLI_dlrbTree.h"
31 #include "BLI_math.h"
32 #include "BLI_utildefines.h"
33 
34 #include "BLT_translation.h"
35 
36 #include "DNA_anim_types.h"
37 #include "DNA_armature_types.h"
38 #include "DNA_curve_types.h"
39 #include "DNA_lattice_types.h"
40 #include "DNA_mask_types.h"
41 #include "DNA_mesh_types.h"
42 #include "DNA_meta_types.h"
43 #include "DNA_node_types.h"
44 #include "DNA_object_types.h"
45 #include "DNA_scene_types.h"
46 #include "DNA_userdef_types.h"
47 #include "DNA_workspace_types.h"
48 
49 #include "BKE_context.h"
50 #include "BKE_editmesh.h"
51 #include "BKE_fcurve.h"
52 #include "BKE_global.h"
53 #include "BKE_icons.h"
54 #include "BKE_lib_id.h"
55 #include "BKE_main.h"
56 #include "BKE_mask.h"
57 #include "BKE_object.h"
58 #include "BKE_report.h"
59 #include "BKE_scene.h"
60 #include "BKE_screen.h"
61 #include "BKE_sound.h"
62 #include "BKE_workspace.h"
63 
64 #include "WM_api.h"
65 #include "WM_types.h"
66 
67 #include "DEG_depsgraph.h"
68 #include "DEG_depsgraph_query.h"
69 
70 #include "ED_anim_api.h"
71 #include "ED_armature.h"
72 #include "ED_clip.h"
73 #include "ED_image.h"
74 #include "ED_keyframes_draw.h"
75 #include "ED_mesh.h"
76 #include "ED_object.h"
77 #include "ED_screen.h"
78 #include "ED_screen_types.h"
79 #include "ED_sequencer.h"
80 #include "ED_undo.h"
81 #include "ED_util.h"
82 #include "ED_view3d.h"
83 
84 #include "RNA_access.h"
85 #include "RNA_define.h"
86 #include "RNA_enum_types.h"
87 
88 #include "UI_interface.h"
89 #include "UI_resources.h"
90 #include "UI_view2d.h"
91 
92 #include "GPU_capabilities.h"
93 
94 #include "screen_intern.h" /* own module include */
95 
96 #define KM_MODAL_CANCEL 1
97 #define KM_MODAL_APPLY 2
98 #define KM_MODAL_SNAP_ON 3
99 #define KM_MODAL_SNAP_OFF 4
100 
101 /* -------------------------------------------------------------------- */
102 /** \name Public Poll API
103  * \{ */
104 
ED_operator_regionactive(bContext * C)105 bool ED_operator_regionactive(bContext *C)
106 {
107   if (CTX_wm_window(C) == NULL) {
108     return false;
109   }
110   if (CTX_wm_screen(C) == NULL) {
111     return false;
112   }
113   if (CTX_wm_region(C) == NULL) {
114     return false;
115   }
116   return true;
117 }
118 
ED_operator_areaactive(bContext * C)119 bool ED_operator_areaactive(bContext *C)
120 {
121   if (CTX_wm_window(C) == NULL) {
122     return false;
123   }
124   if (CTX_wm_screen(C) == NULL) {
125     return false;
126   }
127   if (CTX_wm_area(C) == NULL) {
128     return false;
129   }
130   return true;
131 }
132 
ED_operator_screenactive(bContext * C)133 bool ED_operator_screenactive(bContext *C)
134 {
135   if (CTX_wm_window(C) == NULL) {
136     return false;
137   }
138   if (CTX_wm_screen(C) == NULL) {
139     return false;
140   }
141   return true;
142 }
143 
144 /* XXX added this to prevent anim state to change during renders */
ED_operator_screenactive_norender(bContext * C)145 static bool ED_operator_screenactive_norender(bContext *C)
146 {
147   if (G.is_rendering) {
148     return false;
149   }
150   if (CTX_wm_window(C) == NULL) {
151     return false;
152   }
153   if (CTX_wm_screen(C) == NULL) {
154     return false;
155   }
156   return true;
157 }
158 
159 /* when mouse is over area-edge */
ED_operator_screen_mainwinactive(bContext * C)160 bool ED_operator_screen_mainwinactive(bContext *C)
161 {
162   if (CTX_wm_window(C) == NULL) {
163     return false;
164   }
165   bScreen *screen = CTX_wm_screen(C);
166   if (screen == NULL) {
167     return false;
168   }
169   if (screen->active_region != NULL) {
170     return false;
171   }
172   return true;
173 }
174 
ED_operator_scene(bContext * C)175 bool ED_operator_scene(bContext *C)
176 {
177   Scene *scene = CTX_data_scene(C);
178   if (scene) {
179     return true;
180   }
181   return false;
182 }
183 
ED_operator_scene_editable(bContext * C)184 bool ED_operator_scene_editable(bContext *C)
185 {
186   Scene *scene = CTX_data_scene(C);
187   if (scene && !ID_IS_LINKED(scene)) {
188     return true;
189   }
190   return false;
191 }
192 
ED_operator_objectmode(bContext * C)193 bool ED_operator_objectmode(bContext *C)
194 {
195   Scene *scene = CTX_data_scene(C);
196   Object *obact = CTX_data_active_object(C);
197 
198   if (scene == NULL || ID_IS_LINKED(scene)) {
199     return false;
200   }
201   if (CTX_data_edit_object(C)) {
202     return false;
203   }
204 
205   /* add a check for ob->mode too? */
206   if (obact && (obact->mode != OB_MODE_OBJECT)) {
207     return false;
208   }
209 
210   return true;
211 }
212 
ed_spacetype_test(bContext * C,int type)213 static bool ed_spacetype_test(bContext *C, int type)
214 {
215   if (ED_operator_areaactive(C)) {
216     SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
217     return sl && (sl->spacetype == type);
218   }
219   return false;
220 }
221 
ED_operator_view3d_active(bContext * C)222 bool ED_operator_view3d_active(bContext *C)
223 {
224   return ed_spacetype_test(C, SPACE_VIEW3D);
225 }
226 
ED_operator_region_view3d_active(bContext * C)227 bool ED_operator_region_view3d_active(bContext *C)
228 {
229   if (CTX_wm_region_view3d(C)) {
230     return true;
231   }
232 
233   CTX_wm_operator_poll_msg_set(C, "expected a view3d region");
234   return false;
235 }
236 
237 /* generic for any view2d which uses anim_ops */
ED_operator_animview_active(bContext * C)238 bool ED_operator_animview_active(bContext *C)
239 {
240   if (ED_operator_areaactive(C)) {
241     SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
242     if (sl && (ELEM(sl->spacetype, SPACE_SEQ, SPACE_ACTION, SPACE_NLA, SPACE_GRAPH))) {
243       return true;
244     }
245   }
246 
247   CTX_wm_operator_poll_msg_set(C, "expected a timeline/animation area to be active");
248   return false;
249 }
250 
ED_operator_outliner_active(bContext * C)251 bool ED_operator_outliner_active(bContext *C)
252 {
253   return ed_spacetype_test(C, SPACE_OUTLINER);
254 }
255 
ED_operator_outliner_active_no_editobject(bContext * C)256 bool ED_operator_outliner_active_no_editobject(bContext *C)
257 {
258   if (ed_spacetype_test(C, SPACE_OUTLINER)) {
259     Object *ob = ED_object_active_context(C);
260     Object *obedit = CTX_data_edit_object(C);
261     if (ob && ob == obedit) {
262       return false;
263     }
264     return true;
265   }
266   return false;
267 }
268 
ED_operator_file_active(bContext * C)269 bool ED_operator_file_active(bContext *C)
270 {
271   return ed_spacetype_test(C, SPACE_FILE);
272 }
273 
ED_operator_action_active(bContext * C)274 bool ED_operator_action_active(bContext *C)
275 {
276   return ed_spacetype_test(C, SPACE_ACTION);
277 }
278 
ED_operator_buttons_active(bContext * C)279 bool ED_operator_buttons_active(bContext *C)
280 {
281   return ed_spacetype_test(C, SPACE_PROPERTIES);
282 }
283 
ED_operator_node_active(bContext * C)284 bool ED_operator_node_active(bContext *C)
285 {
286   SpaceNode *snode = CTX_wm_space_node(C);
287 
288   if (snode && snode->edittree) {
289     return true;
290   }
291 
292   return false;
293 }
294 
ED_operator_node_editable(bContext * C)295 bool ED_operator_node_editable(bContext *C)
296 {
297   SpaceNode *snode = CTX_wm_space_node(C);
298 
299   if (snode && snode->edittree && !ID_IS_LINKED(snode->edittree)) {
300     return true;
301   }
302 
303   return false;
304 }
305 
ED_operator_graphedit_active(bContext * C)306 bool ED_operator_graphedit_active(bContext *C)
307 {
308   return ed_spacetype_test(C, SPACE_GRAPH);
309 }
310 
ED_operator_sequencer_active(bContext * C)311 bool ED_operator_sequencer_active(bContext *C)
312 {
313   return ed_spacetype_test(C, SPACE_SEQ);
314 }
315 
ED_operator_sequencer_active_editable(bContext * C)316 bool ED_operator_sequencer_active_editable(bContext *C)
317 {
318   return ed_spacetype_test(C, SPACE_SEQ) && ED_operator_scene_editable(C);
319 }
320 
ED_operator_image_active(bContext * C)321 bool ED_operator_image_active(bContext *C)
322 {
323   return ed_spacetype_test(C, SPACE_IMAGE);
324 }
325 
ED_operator_nla_active(bContext * C)326 bool ED_operator_nla_active(bContext *C)
327 {
328   return ed_spacetype_test(C, SPACE_NLA);
329 }
330 
ED_operator_info_active(bContext * C)331 bool ED_operator_info_active(bContext *C)
332 {
333   return ed_spacetype_test(C, SPACE_INFO);
334 }
335 
ED_operator_console_active(bContext * C)336 bool ED_operator_console_active(bContext *C)
337 {
338   return ed_spacetype_test(C, SPACE_CONSOLE);
339 }
340 
ed_object_hidden(const Object * ob)341 static bool ed_object_hidden(const Object *ob)
342 {
343   /* if hidden but in edit mode, we still display, can happen with animation */
344   return ((ob->restrictflag & OB_RESTRICT_VIEWPORT) && !(ob->mode & OB_MODE_EDIT));
345 }
346 
ED_operator_object_active(bContext * C)347 bool ED_operator_object_active(bContext *C)
348 {
349   Object *ob = ED_object_active_context(C);
350   return ((ob != NULL) && !ed_object_hidden(ob));
351 }
352 
ED_operator_object_active_editable_ex(bContext * UNUSED (C),const Object * ob)353 bool ED_operator_object_active_editable_ex(bContext *UNUSED(C), const Object *ob)
354 {
355   return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob));
356 }
357 
ED_operator_object_active_editable(bContext * C)358 bool ED_operator_object_active_editable(bContext *C)
359 {
360   Object *ob = ED_object_active_context(C);
361   return ED_operator_object_active_editable_ex(C, ob);
362 }
363 
364 /** Object must be editable and fully local (i.e. not an override). */
ED_operator_object_active_local_editable_ex(bContext * C,const Object * ob)365 bool ED_operator_object_active_local_editable_ex(bContext *C, const Object *ob)
366 {
367   return ED_operator_object_active_editable_ex(C, ob) && !ID_IS_OVERRIDE_LIBRARY(ob);
368 }
369 
ED_operator_object_active_local_editable(bContext * C)370 bool ED_operator_object_active_local_editable(bContext *C)
371 {
372   Object *ob = ED_object_active_context(C);
373   return ED_operator_object_active_editable_ex(C, ob) && !ID_IS_OVERRIDE_LIBRARY(ob);
374 }
375 
ED_operator_object_active_editable_mesh(bContext * C)376 bool ED_operator_object_active_editable_mesh(bContext *C)
377 {
378   Object *ob = ED_object_active_context(C);
379   return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob) && (ob->type == OB_MESH) &&
380           !ID_IS_LINKED(ob->data) && !ID_IS_OVERRIDE_LIBRARY(ob->data));
381 }
382 
ED_operator_object_active_editable_font(bContext * C)383 bool ED_operator_object_active_editable_font(bContext *C)
384 {
385   Object *ob = ED_object_active_context(C);
386   return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob) && (ob->type == OB_FONT) &&
387           !ID_IS_LINKED(ob->data) && !ID_IS_OVERRIDE_LIBRARY(ob->data));
388 }
389 
ED_operator_editable_mesh(bContext * C)390 bool ED_operator_editable_mesh(bContext *C)
391 {
392   Mesh *mesh = ED_mesh_context(C);
393   return (mesh != NULL) && !ID_IS_LINKED(mesh) && !ID_IS_OVERRIDE_LIBRARY(mesh);
394 }
395 
ED_operator_editmesh(bContext * C)396 bool ED_operator_editmesh(bContext *C)
397 {
398   Object *obedit = CTX_data_edit_object(C);
399   if (obedit && obedit->type == OB_MESH) {
400     return NULL != BKE_editmesh_from_object(obedit);
401   }
402   return false;
403 }
404 
ED_operator_editmesh_view3d(bContext * C)405 bool ED_operator_editmesh_view3d(bContext *C)
406 {
407   return ED_operator_editmesh(C) && ED_operator_view3d_active(C);
408 }
409 
ED_operator_editmesh_region_view3d(bContext * C)410 bool ED_operator_editmesh_region_view3d(bContext *C)
411 {
412   if (ED_operator_editmesh(C) && CTX_wm_region_view3d(C)) {
413     return true;
414   }
415 
416   CTX_wm_operator_poll_msg_set(C, "expected a view3d region & editmesh");
417   return false;
418 }
419 
ED_operator_editmesh_auto_smooth(bContext * C)420 bool ED_operator_editmesh_auto_smooth(bContext *C)
421 {
422   Object *obedit = CTX_data_edit_object(C);
423   if (obedit && obedit->type == OB_MESH && (((Mesh *)(obedit->data))->flag & ME_AUTOSMOOTH)) {
424     return NULL != BKE_editmesh_from_object(obedit);
425   }
426   return false;
427 }
428 
ED_operator_editarmature(bContext * C)429 bool ED_operator_editarmature(bContext *C)
430 {
431   Object *obedit = CTX_data_edit_object(C);
432   if (obedit && obedit->type == OB_ARMATURE) {
433     return NULL != ((bArmature *)obedit->data)->edbo;
434   }
435   return false;
436 }
437 
438 /**
439  * \brief check for pose mode (no mixed modes)
440  *
441  * We want to enable most pose operations in weight paint mode,
442  * when it comes to transforming bones, but managing bones layers/groups
443  * can be left for pose mode only. (not weight paint mode)
444  */
ED_operator_posemode_exclusive(bContext * C)445 bool ED_operator_posemode_exclusive(bContext *C)
446 {
447   Object *obact = CTX_data_active_object(C);
448 
449   if (obact && !(obact->mode & OB_MODE_EDIT)) {
450     Object *obpose = BKE_object_pose_armature_get(obact);
451     if (obpose != NULL) {
452       if (obact == obpose) {
453         return true;
454       }
455     }
456   }
457 
458   return false;
459 }
460 
461 /* allows for pinned pose objects to be used in the object buttons
462  * and the non-active pose object to be used in the 3D view */
ED_operator_posemode_context(bContext * C)463 bool ED_operator_posemode_context(bContext *C)
464 {
465   Object *obpose = ED_pose_object_from_context(C);
466 
467   if (obpose && !(obpose->mode & OB_MODE_EDIT)) {
468     if (BKE_object_pose_context_check(obpose)) {
469       return true;
470     }
471   }
472 
473   return false;
474 }
475 
ED_operator_posemode(bContext * C)476 bool ED_operator_posemode(bContext *C)
477 {
478   Object *obact = CTX_data_active_object(C);
479 
480   if (obact && !(obact->mode & OB_MODE_EDIT)) {
481     Object *obpose = BKE_object_pose_armature_get(obact);
482     if (obpose != NULL) {
483       if ((obact == obpose) || (obact->mode & OB_MODE_ALL_WEIGHT_PAINT)) {
484         return true;
485       }
486     }
487   }
488 
489   return false;
490 }
491 
ED_operator_posemode_local(bContext * C)492 bool ED_operator_posemode_local(bContext *C)
493 {
494   if (ED_operator_posemode(C)) {
495     Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
496     bArmature *arm = ob->data;
497     return !(ID_IS_LINKED(&ob->id) || ID_IS_LINKED(&arm->id));
498   }
499   return false;
500 }
501 
502 /* wrapper for ED_space_image_show_uvedit */
ED_operator_uvedit(bContext * C)503 bool ED_operator_uvedit(bContext *C)
504 {
505   SpaceImage *sima = CTX_wm_space_image(C);
506   Object *obedit = CTX_data_edit_object(C);
507   return ED_space_image_show_uvedit(sima, obedit);
508 }
509 
ED_operator_uvedit_space_image(bContext * C)510 bool ED_operator_uvedit_space_image(bContext *C)
511 {
512   SpaceImage *sima = CTX_wm_space_image(C);
513   Object *obedit = CTX_data_edit_object(C);
514   return sima && ED_space_image_show_uvedit(sima, obedit);
515 }
516 
ED_operator_uvmap(bContext * C)517 bool ED_operator_uvmap(bContext *C)
518 {
519   Object *obedit = CTX_data_edit_object(C);
520   BMEditMesh *em = NULL;
521 
522   if (obedit && obedit->type == OB_MESH) {
523     em = BKE_editmesh_from_object(obedit);
524   }
525 
526   if (em && (em->bm->totface)) {
527     return true;
528   }
529 
530   return false;
531 }
532 
ED_operator_editsurfcurve(bContext * C)533 bool ED_operator_editsurfcurve(bContext *C)
534 {
535   Object *obedit = CTX_data_edit_object(C);
536   if (obedit && ELEM(obedit->type, OB_CURVE, OB_SURF)) {
537     return NULL != ((Curve *)obedit->data)->editnurb;
538   }
539   return false;
540 }
541 
ED_operator_editsurfcurve_region_view3d(bContext * C)542 bool ED_operator_editsurfcurve_region_view3d(bContext *C)
543 {
544   if (ED_operator_editsurfcurve(C) && CTX_wm_region_view3d(C)) {
545     return true;
546   }
547 
548   CTX_wm_operator_poll_msg_set(C, "expected a view3d region & editcurve");
549   return false;
550 }
551 
ED_operator_editcurve(bContext * C)552 bool ED_operator_editcurve(bContext *C)
553 {
554   Object *obedit = CTX_data_edit_object(C);
555   if (obedit && obedit->type == OB_CURVE) {
556     return NULL != ((Curve *)obedit->data)->editnurb;
557   }
558   return false;
559 }
560 
ED_operator_editcurve_3d(bContext * C)561 bool ED_operator_editcurve_3d(bContext *C)
562 {
563   Object *obedit = CTX_data_edit_object(C);
564   if (obedit && obedit->type == OB_CURVE) {
565     Curve *cu = (Curve *)obedit->data;
566 
567     return (cu->flag & CU_3D) && (NULL != cu->editnurb);
568   }
569   return false;
570 }
571 
ED_operator_editsurf(bContext * C)572 bool ED_operator_editsurf(bContext *C)
573 {
574   Object *obedit = CTX_data_edit_object(C);
575   if (obedit && obedit->type == OB_SURF) {
576     return NULL != ((Curve *)obedit->data)->editnurb;
577   }
578   return false;
579 }
580 
ED_operator_editfont(bContext * C)581 bool ED_operator_editfont(bContext *C)
582 {
583   Object *obedit = CTX_data_edit_object(C);
584   if (obedit && obedit->type == OB_FONT) {
585     return NULL != ((Curve *)obedit->data)->editfont;
586   }
587   return false;
588 }
589 
ED_operator_editlattice(bContext * C)590 bool ED_operator_editlattice(bContext *C)
591 {
592   Object *obedit = CTX_data_edit_object(C);
593   if (obedit && obedit->type == OB_LATTICE) {
594     return NULL != ((Lattice *)obedit->data)->editlatt;
595   }
596   return false;
597 }
598 
ED_operator_editmball(bContext * C)599 bool ED_operator_editmball(bContext *C)
600 {
601   Object *obedit = CTX_data_edit_object(C);
602   if (obedit && obedit->type == OB_MBALL) {
603     return NULL != ((MetaBall *)obedit->data)->editelems;
604   }
605   return false;
606 }
607 
ED_operator_mask(bContext * C)608 bool ED_operator_mask(bContext *C)
609 {
610   ScrArea *area = CTX_wm_area(C);
611   if (area && area->spacedata.first) {
612     switch (area->spacetype) {
613       case SPACE_CLIP: {
614         SpaceClip *screen = area->spacedata.first;
615         return ED_space_clip_check_show_maskedit(screen);
616       }
617       case SPACE_SEQ: {
618         SpaceSeq *sseq = area->spacedata.first;
619         Scene *scene = CTX_data_scene(C);
620         return ED_space_sequencer_check_show_maskedit(sseq, scene);
621       }
622       case SPACE_IMAGE: {
623         SpaceImage *sima = area->spacedata.first;
624         ViewLayer *view_layer = CTX_data_view_layer(C);
625         Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
626         return ED_space_image_check_show_maskedit(sima, obedit);
627       }
628     }
629   }
630 
631   return false;
632 }
633 
ED_operator_camera(bContext * C)634 bool ED_operator_camera(bContext *C)
635 {
636   struct Camera *cam = CTX_data_pointer_get_type(C, "camera", &RNA_Camera).data;
637   return (cam != NULL);
638 }
639 
640 /** \} */
641 
642 /* -------------------------------------------------------------------- */
643 /** \name Internal Screen Utilities
644  * \{ */
645 
screen_active_editable(bContext * C)646 static bool screen_active_editable(bContext *C)
647 {
648   if (ED_operator_screenactive(C)) {
649     /* no full window splitting allowed */
650     if (CTX_wm_screen(C)->state != SCREENNORMAL) {
651       return false;
652     }
653     return true;
654   }
655   return false;
656 }
657 
658 /** \} */
659 
660 /* -------------------------------------------------------------------- */
661 /** \name Action Zone Operator
662  * \{ */
663 
664 /* operator state vars used:
665  * none
666  *
667  * functions:
668  *
669  * apply() set action-zone event
670  *
671  * exit()   free customdata
672  *
673  * callbacks:
674  *
675  * exec()   never used
676  *
677  * invoke() check if in zone
678  * add customdata, put mouseco and area in it
679  * add modal handler
680  *
681  * modal()  accept modal events while doing it
682  * call apply() with gesture info, active window, nonactive window
683  * call exit() and remove handler when LMB confirm
684  */
685 
686 typedef struct sActionzoneData {
687   ScrArea *sa1, *sa2;
688   AZone *az;
689   int x, y, gesture_dir, modifier;
690 } sActionzoneData;
691 
692 /* quick poll to save operators to be created and handled */
actionzone_area_poll(bContext * C)693 static bool actionzone_area_poll(bContext *C)
694 {
695   wmWindow *win = CTX_wm_window(C);
696   bScreen *screen = WM_window_get_active_screen(win);
697 
698   if (screen && win && win->eventstate) {
699     const int *xy = &win->eventstate->x;
700 
701     LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
702       LISTBASE_FOREACH (AZone *, az, &area->actionzones) {
703         if (BLI_rcti_isect_pt_v(&az->rect, xy)) {
704           return true;
705         }
706       }
707     }
708   }
709   return false;
710 }
711 
712 /* the debug drawing of the click_rect is in area_draw_azone_fullscreen, keep both in sync */
fullscreen_click_rcti_init(rcti * rect,const short UNUSED (x1),const short UNUSED (y1),const short x2,const short y2)713 static void fullscreen_click_rcti_init(
714     rcti *rect, const short UNUSED(x1), const short UNUSED(y1), const short x2, const short y2)
715 {
716   BLI_rcti_init(rect, x2 - U.widget_unit, x2, y2 - U.widget_unit, y2);
717 }
718 
azone_clipped_rect_calc(const AZone * az,rcti * r_rect_clip)719 static bool azone_clipped_rect_calc(const AZone *az, rcti *r_rect_clip)
720 {
721   const ARegion *region = az->region;
722   *r_rect_clip = az->rect;
723   if (az->type == AZONE_REGION) {
724     if (region->overlap && (region->v2d.keeptot != V2D_KEEPTOT_STRICT) &&
725         /* Only when this isn't hidden (where it's displayed as an button that expands). */
726         ((az->region->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) == 0)) {
727       /* A floating region to be resized, clip by the visible region. */
728       switch (az->edge) {
729         case AE_TOP_TO_BOTTOMRIGHT:
730         case AE_BOTTOM_TO_TOPLEFT: {
731           r_rect_clip->xmin = max_ii(
732               r_rect_clip->xmin,
733               (region->winrct.xmin +
734                UI_view2d_view_to_region_x(&region->v2d, region->v2d.tot.xmin)) -
735                   UI_REGION_OVERLAP_MARGIN);
736           r_rect_clip->xmax = min_ii(
737               r_rect_clip->xmax,
738               (region->winrct.xmin +
739                UI_view2d_view_to_region_x(&region->v2d, region->v2d.tot.xmax)) +
740                   UI_REGION_OVERLAP_MARGIN);
741           return true;
742         }
743         case AE_LEFT_TO_TOPRIGHT:
744         case AE_RIGHT_TO_TOPLEFT: {
745           r_rect_clip->ymin = max_ii(
746               r_rect_clip->ymin,
747               (region->winrct.ymin +
748                UI_view2d_view_to_region_y(&region->v2d, region->v2d.tot.ymin)) -
749                   UI_REGION_OVERLAP_MARGIN);
750           r_rect_clip->ymax = min_ii(
751               r_rect_clip->ymax,
752               (region->winrct.ymin +
753                UI_view2d_view_to_region_y(&region->v2d, region->v2d.tot.ymax)) +
754                   UI_REGION_OVERLAP_MARGIN);
755           return true;
756         }
757       }
758     }
759   }
760   return false;
761 }
762 
area_actionzone_refresh_xy(ScrArea * area,const int xy[2],const bool test_only)763 static AZone *area_actionzone_refresh_xy(ScrArea *area, const int xy[2], const bool test_only)
764 {
765   AZone *az = NULL;
766 
767   for (az = area->actionzones.first; az; az = az->next) {
768     rcti az_rect_clip;
769     if (BLI_rcti_isect_pt_v(&az->rect, xy) &&
770         /* Check clipping if this is clipped */
771         (!azone_clipped_rect_calc(az, &az_rect_clip) || BLI_rcti_isect_pt_v(&az_rect_clip, xy))) {
772 
773       if (az->type == AZONE_AREA) {
774         break;
775       }
776       if (az->type == AZONE_REGION) {
777         break;
778       }
779       if (az->type == AZONE_FULLSCREEN) {
780         rcti click_rect;
781         fullscreen_click_rcti_init(&click_rect, az->x1, az->y1, az->x2, az->y2);
782         const bool click_isect = BLI_rcti_isect_pt_v(&click_rect, xy);
783 
784         if (test_only) {
785           if (click_isect) {
786             break;
787           }
788         }
789         else {
790           if (click_isect) {
791             az->alpha = 1.0f;
792           }
793           else {
794             const int mouse_sq = square_i(xy[0] - az->x2) + square_i(xy[1] - az->y2);
795             const int spot_sq = square_i(AZONESPOTW);
796             const int fadein_sq = square_i(AZONEFADEIN);
797             const int fadeout_sq = square_i(AZONEFADEOUT);
798 
799             if (mouse_sq < spot_sq) {
800               az->alpha = 1.0f;
801             }
802             else if (mouse_sq < fadein_sq) {
803               az->alpha = 1.0f;
804             }
805             else if (mouse_sq < fadeout_sq) {
806               az->alpha = 1.0f -
807                           ((float)(mouse_sq - fadein_sq)) / ((float)(fadeout_sq - fadein_sq));
808             }
809             else {
810               az->alpha = 0.0f;
811             }
812 
813             /* fade in/out but no click */
814             az = NULL;
815           }
816 
817           /* XXX force redraw to show/hide the action zone */
818           ED_area_tag_redraw(area);
819           break;
820         }
821       }
822       else if (az->type == AZONE_REGION_SCROLL) {
823         ARegion *region = az->region;
824         View2D *v2d = &region->v2d;
825         int scroll_flag = 0;
826         const int isect_value = UI_view2d_mouse_in_scrollers_ex(
827             region, v2d, xy[0], xy[1], &scroll_flag);
828 
829         /* Check if we even have scroll bars. */
830         if (((az->direction == AZ_SCROLL_HOR) && !(scroll_flag & V2D_SCROLL_HORIZONTAL)) ||
831             ((az->direction == AZ_SCROLL_VERT) && !(scroll_flag & V2D_SCROLL_VERTICAL))) {
832           /* no scrollbars, do nothing. */
833         }
834         else if (test_only) {
835           if (isect_value != 0) {
836             break;
837           }
838         }
839         else {
840           bool redraw = false;
841 
842           if (isect_value == 'h') {
843             if (az->direction == AZ_SCROLL_HOR) {
844               az->alpha = 1.0f;
845               v2d->alpha_hor = 255;
846               redraw = true;
847             }
848           }
849           else if (isect_value == 'v') {
850             if (az->direction == AZ_SCROLL_VERT) {
851               az->alpha = 1.0f;
852               v2d->alpha_vert = 255;
853               redraw = true;
854             }
855           }
856           else {
857             const int local_xy[2] = {xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin};
858             float dist_fac = 0.0f, alpha = 0.0f;
859 
860             if (az->direction == AZ_SCROLL_HOR) {
861               dist_fac = BLI_rcti_length_y(&v2d->hor, local_xy[1]) / AZONEFADEIN;
862               CLAMP(dist_fac, 0.0f, 1.0f);
863               alpha = 1.0f - dist_fac;
864 
865               v2d->alpha_hor = alpha * 255;
866             }
867             else if (az->direction == AZ_SCROLL_VERT) {
868               dist_fac = BLI_rcti_length_x(&v2d->vert, local_xy[0]) / AZONEFADEIN;
869               CLAMP(dist_fac, 0.0f, 1.0f);
870               alpha = 1.0f - dist_fac;
871 
872               v2d->alpha_vert = alpha * 255;
873             }
874             az->alpha = alpha;
875             redraw = true;
876           }
877 
878           if (redraw) {
879             ED_region_tag_redraw_no_rebuild(region);
880           }
881           /* Don't return! */
882         }
883       }
884     }
885     else if (!test_only && !IS_EQF(az->alpha, 0.0f)) {
886       if (az->type == AZONE_FULLSCREEN) {
887         az->alpha = 0.0f;
888         area->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
889         ED_area_tag_redraw_no_rebuild(area);
890       }
891       else if (az->type == AZONE_REGION_SCROLL) {
892         if (az->direction == AZ_SCROLL_VERT) {
893           az->alpha = az->region->v2d.alpha_vert = 0;
894           area->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
895           ED_region_tag_redraw_no_rebuild(az->region);
896         }
897         else if (az->direction == AZ_SCROLL_HOR) {
898           az->alpha = az->region->v2d.alpha_hor = 0;
899           area->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
900           ED_region_tag_redraw_no_rebuild(az->region);
901         }
902         else {
903           BLI_assert(false);
904         }
905       }
906     }
907   }
908 
909   return az;
910 }
911 
912 /* Finds an action-zone by position in entire screen so azones can overlap. */
screen_actionzone_find_xy(bScreen * screen,const int xy[2])913 static AZone *screen_actionzone_find_xy(bScreen *screen, const int xy[2])
914 {
915   LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
916     AZone *az = area_actionzone_refresh_xy(area, xy, true);
917     if (az != NULL) {
918       return az;
919     }
920   }
921   return NULL;
922 }
923 
924 /* Returns the area that the azone belongs to */
screen_actionzone_area(bScreen * screen,const AZone * az)925 static ScrArea *screen_actionzone_area(bScreen *screen, const AZone *az)
926 {
927   LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
928     LISTBASE_FOREACH (AZone *, zone, &area->actionzones) {
929       if (zone == az) {
930         return area;
931       }
932     }
933   }
934   return NULL;
935 }
936 
ED_area_actionzone_find_xy(ScrArea * area,const int xy[2])937 AZone *ED_area_actionzone_find_xy(ScrArea *area, const int xy[2])
938 {
939   return area_actionzone_refresh_xy(area, xy, true);
940 }
941 
ED_area_azones_update(ScrArea * area,const int xy[2])942 AZone *ED_area_azones_update(ScrArea *area, const int xy[2])
943 {
944   return area_actionzone_refresh_xy(area, xy, false);
945 }
946 
actionzone_exit(wmOperator * op)947 static void actionzone_exit(wmOperator *op)
948 {
949   if (op->customdata) {
950     MEM_freeN(op->customdata);
951   }
952   op->customdata = NULL;
953 
954   G.moving &= ~G_TRANSFORM_WM;
955 }
956 
957 /* send EVT_ACTIONZONE event */
actionzone_apply(bContext * C,wmOperator * op,int type)958 static void actionzone_apply(bContext *C, wmOperator *op, int type)
959 {
960   wmWindow *win = CTX_wm_window(C);
961   sActionzoneData *sad = op->customdata;
962 
963   sad->modifier = RNA_int_get(op->ptr, "modifier");
964 
965   wmEvent event;
966   wm_event_init_from_window(win, &event);
967 
968   if (type == AZONE_AREA) {
969     event.type = EVT_ACTIONZONE_AREA;
970   }
971   else if (type == AZONE_FULLSCREEN) {
972     event.type = EVT_ACTIONZONE_FULLSCREEN;
973   }
974   else {
975     event.type = EVT_ACTIONZONE_REGION;
976   }
977 
978   event.val = KM_NOTHING;
979   event.is_repeat = false;
980   event.customdata = op->customdata;
981   event.customdatafree = true;
982   op->customdata = NULL;
983 
984   wm_event_add(win, &event);
985 }
986 
actionzone_invoke(bContext * C,wmOperator * op,const wmEvent * event)987 static int actionzone_invoke(bContext *C, wmOperator *op, const wmEvent *event)
988 {
989   bScreen *screen = CTX_wm_screen(C);
990   AZone *az = screen_actionzone_find_xy(screen, &event->x);
991 
992   /* Quick escape - Scroll azones only hide/unhide the scroll-bars,
993    * they have their own handling. */
994   if (az == NULL || ELEM(az->type, AZONE_REGION_SCROLL)) {
995     return OPERATOR_PASS_THROUGH;
996   }
997 
998   /* ok we do the action-zone */
999   sActionzoneData *sad = op->customdata = MEM_callocN(sizeof(sActionzoneData), "sActionzoneData");
1000   sad->sa1 = screen_actionzone_area(screen, az);
1001   sad->az = az;
1002   sad->x = event->x;
1003   sad->y = event->y;
1004 
1005   /* region azone directly reacts on mouse clicks */
1006   if (ELEM(sad->az->type, AZONE_REGION, AZONE_FULLSCREEN)) {
1007     actionzone_apply(C, op, sad->az->type);
1008     actionzone_exit(op);
1009     return OPERATOR_FINISHED;
1010   }
1011 
1012   BLI_assert(ELEM(sad->az->type, AZONE_AREA, AZONE_REGION_SCROLL));
1013 
1014   /* add modal handler */
1015   G.moving |= G_TRANSFORM_WM;
1016   WM_event_add_modal_handler(C, op);
1017   return OPERATOR_RUNNING_MODAL;
1018 }
1019 
actionzone_modal(bContext * C,wmOperator * op,const wmEvent * event)1020 static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event)
1021 {
1022   bScreen *screen = CTX_wm_screen(C);
1023   sActionzoneData *sad = op->customdata;
1024 
1025   switch (event->type) {
1026     case MOUSEMOVE: {
1027       const int delta_x = (event->x - sad->x);
1028       const int delta_y = (event->y - sad->y);
1029 
1030       /* Movement in dominant direction. */
1031       const int delta_max = max_ii(abs(delta_x), abs(delta_y));
1032 
1033       /* Movement in dominant direction before action taken. */
1034       const int join_threshold = (0.6 * U.widget_unit);
1035       const int split_threshold = (1.2 * U.widget_unit);
1036       const int area_threshold = (0.1 * U.widget_unit);
1037 
1038       /* Calculate gesture cardinal direction. */
1039       if (delta_y > abs(delta_x)) {
1040         sad->gesture_dir = 'n';
1041       }
1042       else if (delta_x >= abs(delta_y)) {
1043         sad->gesture_dir = 'e';
1044       }
1045       else if (delta_y < -abs(delta_x)) {
1046         sad->gesture_dir = 's';
1047       }
1048       else {
1049         sad->gesture_dir = 'w';
1050       }
1051 
1052       bool is_gesture;
1053       if (sad->az->type == AZONE_AREA) {
1054         wmWindow *win = CTX_wm_window(C);
1055 
1056         rcti screen_rect;
1057         WM_window_screen_rect_calc(win, &screen_rect);
1058 
1059         /* Have we dragged off the zone and are not on an edge? */
1060         if ((ED_area_actionzone_find_xy(sad->sa1, &event->x) != sad->az) &&
1061             (screen_geom_area_map_find_active_scredge(
1062                  AREAMAP_FROM_SCREEN(screen), &screen_rect, event->x, event->y) == NULL)) {
1063           /* Are we still in same area? */
1064           if (BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) {
1065             /* Same area, so possible split. */
1066             WM_cursor_set(
1067                 win, (ELEM(sad->gesture_dir, 'n', 's')) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT);
1068             is_gesture = (delta_max > split_threshold);
1069           }
1070           else {
1071             /* Different area, so possible join. */
1072             if (sad->gesture_dir == 'n') {
1073               WM_cursor_set(win, WM_CURSOR_N_ARROW);
1074             }
1075             else if (sad->gesture_dir == 's') {
1076               WM_cursor_set(win, WM_CURSOR_S_ARROW);
1077             }
1078             else if (sad->gesture_dir == 'e') {
1079               WM_cursor_set(win, WM_CURSOR_E_ARROW);
1080             }
1081             else {
1082               WM_cursor_set(win, WM_CURSOR_W_ARROW);
1083             }
1084             is_gesture = (delta_max > join_threshold);
1085           }
1086         }
1087         else {
1088           WM_cursor_set(CTX_wm_window(C), WM_CURSOR_CROSS);
1089           is_gesture = false;
1090         }
1091       }
1092       else {
1093         is_gesture = (delta_max > area_threshold);
1094       }
1095 
1096       /* gesture is large enough? */
1097       if (is_gesture) {
1098         /* second area, for join when (sa1 != sa2) */
1099         sad->sa2 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y);
1100         /* apply sends event */
1101         actionzone_apply(C, op, sad->az->type);
1102         actionzone_exit(op);
1103 
1104         return OPERATOR_FINISHED;
1105       }
1106       break;
1107     }
1108     case EVT_ESCKEY:
1109       actionzone_exit(op);
1110       return OPERATOR_CANCELLED;
1111     case LEFTMOUSE:
1112       actionzone_exit(op);
1113       return OPERATOR_CANCELLED;
1114   }
1115 
1116   return OPERATOR_RUNNING_MODAL;
1117 }
1118 
actionzone_cancel(bContext * UNUSED (C),wmOperator * op)1119 static void actionzone_cancel(bContext *UNUSED(C), wmOperator *op)
1120 {
1121   actionzone_exit(op);
1122 }
1123 
SCREEN_OT_actionzone(wmOperatorType * ot)1124 static void SCREEN_OT_actionzone(wmOperatorType *ot)
1125 {
1126   /* identifiers */
1127   ot->name = "Handle Area Action Zones";
1128   ot->description = "Handle area action zones for mouse actions/gestures";
1129   ot->idname = "SCREEN_OT_actionzone";
1130 
1131   ot->invoke = actionzone_invoke;
1132   ot->modal = actionzone_modal;
1133   ot->poll = actionzone_area_poll;
1134   ot->cancel = actionzone_cancel;
1135 
1136   /* flags */
1137   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
1138 
1139   RNA_def_int(ot->srna, "modifier", 0, 0, 2, "Modifier", "Modifier state", 0, 2);
1140 }
1141 
1142 /** \} */
1143 
1144 /* -------------------------------------------------------------------- */
1145 /** \name Area edge detection utility
1146  * \{ */
1147 
screen_area_edge_from_cursor(const bContext * C,const int cursor[2],ScrArea ** r_sa1,ScrArea ** r_sa2)1148 static ScrEdge *screen_area_edge_from_cursor(const bContext *C,
1149                                              const int cursor[2],
1150                                              ScrArea **r_sa1,
1151                                              ScrArea **r_sa2)
1152 {
1153   wmWindow *win = CTX_wm_window(C);
1154   bScreen *screen = CTX_wm_screen(C);
1155   rcti window_rect;
1156   WM_window_rect_calc(win, &window_rect);
1157   ScrEdge *actedge = screen_geom_area_map_find_active_scredge(
1158       AREAMAP_FROM_SCREEN(screen), &window_rect, cursor[0], cursor[1]);
1159   *r_sa1 = NULL;
1160   *r_sa2 = NULL;
1161   if (actedge == NULL) {
1162     return NULL;
1163   }
1164   int borderwidth = (4 * UI_DPI_FAC);
1165   ScrArea *sa1, *sa2;
1166   if (screen_geom_edge_is_horizontal(actedge)) {
1167     sa1 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0], cursor[1] + borderwidth);
1168     sa2 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0], cursor[1] - borderwidth);
1169   }
1170   else {
1171     sa1 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0] + borderwidth, cursor[1]);
1172     sa2 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, cursor[0] - borderwidth, cursor[1]);
1173   }
1174   bool isGlobal = ((sa1 && ED_area_is_global(sa1)) || (sa2 && ED_area_is_global(sa2)));
1175   if (!isGlobal) {
1176     *r_sa1 = sa1;
1177     *r_sa2 = sa2;
1178   }
1179   return actedge;
1180 }
1181 
1182 /** \} */
1183 
1184 /* -------------------------------------------------------------------- */
1185 /** \name Swap Area Operator
1186  * \{ */
1187 
1188 /* operator state vars used:
1189  * sa1      start area
1190  * sa2      area to swap with
1191  *
1192  * functions:
1193  *
1194  * init()   set custom data for operator, based on action-zone event custom data
1195  *
1196  * cancel() cancel the operator
1197  *
1198  * exit()   cleanup, send notifier
1199  *
1200  * callbacks:
1201  *
1202  * invoke() gets called on shift+lmb drag in action-zone
1203  * exec()   execute without any user interaction, based on properties
1204  * call init(), add handler
1205  *
1206  * modal()  accept modal events while doing it
1207  */
1208 
1209 typedef struct sAreaSwapData {
1210   ScrArea *sa1, *sa2;
1211 } sAreaSwapData;
1212 
area_swap_init(wmOperator * op,const wmEvent * event)1213 static bool area_swap_init(wmOperator *op, const wmEvent *event)
1214 {
1215   sActionzoneData *sad = event->customdata;
1216 
1217   if (sad == NULL || sad->sa1 == NULL) {
1218     return false;
1219   }
1220 
1221   sAreaSwapData *sd = MEM_callocN(sizeof(sAreaSwapData), "sAreaSwapData");
1222   sd->sa1 = sad->sa1;
1223   sd->sa2 = sad->sa2;
1224   op->customdata = sd;
1225 
1226   return true;
1227 }
1228 
area_swap_exit(bContext * C,wmOperator * op)1229 static void area_swap_exit(bContext *C, wmOperator *op)
1230 {
1231   WM_cursor_modal_restore(CTX_wm_window(C));
1232   if (op->customdata) {
1233     MEM_freeN(op->customdata);
1234   }
1235   op->customdata = NULL;
1236 }
1237 
area_swap_cancel(bContext * C,wmOperator * op)1238 static void area_swap_cancel(bContext *C, wmOperator *op)
1239 {
1240   area_swap_exit(C, op);
1241 }
1242 
area_swap_invoke(bContext * C,wmOperator * op,const wmEvent * event)1243 static int area_swap_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1244 {
1245   if (!area_swap_init(op, event)) {
1246     return OPERATOR_PASS_THROUGH;
1247   }
1248 
1249   /* add modal handler */
1250   WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_SWAP_AREA);
1251   WM_event_add_modal_handler(C, op);
1252 
1253   return OPERATOR_RUNNING_MODAL;
1254 }
1255 
area_swap_modal(bContext * C,wmOperator * op,const wmEvent * event)1256 static int area_swap_modal(bContext *C, wmOperator *op, const wmEvent *event)
1257 {
1258   sActionzoneData *sad = op->customdata;
1259 
1260   switch (event->type) {
1261     case MOUSEMOVE:
1262       /* second area, for join */
1263       sad->sa2 = BKE_screen_find_area_xy(CTX_wm_screen(C), SPACE_TYPE_ANY, event->x, event->y);
1264       break;
1265     case LEFTMOUSE: /* release LMB */
1266       if (event->val == KM_RELEASE) {
1267         if (!sad->sa2 || sad->sa1 == sad->sa2) {
1268           area_swap_cancel(C, op);
1269           return OPERATOR_CANCELLED;
1270         }
1271 
1272         ED_area_tag_redraw(sad->sa1);
1273         ED_area_tag_redraw(sad->sa2);
1274 
1275         ED_area_swapspace(C, sad->sa1, sad->sa2);
1276 
1277         area_swap_exit(C, op);
1278 
1279         WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1280 
1281         return OPERATOR_FINISHED;
1282       }
1283       break;
1284 
1285     case EVT_ESCKEY:
1286       area_swap_cancel(C, op);
1287       return OPERATOR_CANCELLED;
1288   }
1289   return OPERATOR_RUNNING_MODAL;
1290 }
1291 
area_swap_exec(bContext * C,wmOperator * op)1292 static int area_swap_exec(bContext *C, wmOperator *op)
1293 {
1294   ScrArea *sa1, *sa2;
1295   int cursor[2];
1296   RNA_int_get_array(op->ptr, "cursor", cursor);
1297   screen_area_edge_from_cursor(C, cursor, &sa1, &sa2);
1298   if (sa1 == NULL || sa2 == NULL) {
1299     return OPERATOR_CANCELLED;
1300   }
1301   ED_area_swapspace(C, sa1, sa2);
1302   return OPERATOR_FINISHED;
1303 }
1304 
SCREEN_OT_area_swap(wmOperatorType * ot)1305 static void SCREEN_OT_area_swap(wmOperatorType *ot)
1306 {
1307   ot->name = "Swap Areas";
1308   ot->description = "Swap selected areas screen positions";
1309   ot->idname = "SCREEN_OT_area_swap";
1310 
1311   ot->invoke = area_swap_invoke;
1312   ot->modal = area_swap_modal;
1313   ot->exec = area_swap_exec;
1314   ot->poll = screen_active_editable;
1315   ot->cancel = area_swap_cancel;
1316 
1317   ot->flag = OPTYPE_BLOCKING;
1318 
1319   /* rna */
1320   RNA_def_int_vector(
1321       ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX);
1322 }
1323 
1324 /** \} */
1325 
1326 /* -------------------------------------------------------------------- */
1327 /** \name Area Duplicate Operator
1328  *
1329  * Create new window from area.
1330  * \{ */
1331 
1332 /* operator callback */
area_dupli_invoke(bContext * C,wmOperator * op,const wmEvent * event)1333 static int area_dupli_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1334 {
1335   Main *bmain = CTX_data_main(C);
1336   wmWindow *win = CTX_wm_window(C);
1337   WorkSpace *workspace = WM_window_get_active_workspace(win);
1338   WorkSpaceLayout *layout_old = WM_window_get_active_layout(win);
1339 
1340   Scene *scene = CTX_data_scene(C);
1341   ScrArea *area = CTX_wm_area(C);
1342 
1343   /* XXX hrmf! */
1344   if (event->type == EVT_ACTIONZONE_AREA) {
1345     sActionzoneData *sad = event->customdata;
1346 
1347     if (sad == NULL) {
1348       return OPERATOR_PASS_THROUGH;
1349     }
1350 
1351     area = sad->sa1;
1352   }
1353 
1354   /* adds window to WM */
1355   rcti rect = area->totrct;
1356   BLI_rcti_translate(&rect, win->posx, win->posy);
1357   rect.xmax = rect.xmin + BLI_rcti_size_x(&rect) / U.pixelsize;
1358   rect.ymax = rect.ymin + BLI_rcti_size_y(&rect) / U.pixelsize;
1359 
1360   wmWindow *newwin = WM_window_open(C, &rect);
1361   if (newwin == NULL) {
1362     BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
1363     goto finally;
1364   }
1365 
1366   *newwin->stereo3d_format = *win->stereo3d_format;
1367 
1368   newwin->scene = scene;
1369 
1370   STRNCPY(newwin->view_layer_name, win->view_layer_name);
1371 
1372   BKE_workspace_active_set(newwin->workspace_hook, workspace);
1373   /* allocs new screen and adds to newly created window, using window size */
1374   WorkSpaceLayout *layout_new = ED_workspace_layout_add(
1375       bmain, workspace, newwin, BKE_workspace_layout_name_get(layout_old));
1376   bScreen *newsc = BKE_workspace_layout_screen_get(layout_new);
1377   WM_window_set_active_layout(newwin, workspace, layout_new);
1378 
1379   /* copy area to new screen */
1380   ED_area_data_copy((ScrArea *)newsc->areabase.first, area, true);
1381 
1382   ED_area_tag_redraw((ScrArea *)newsc->areabase.first);
1383 
1384   /* screen, areas init */
1385   WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1386 
1387 finally:
1388   if (event->type == EVT_ACTIONZONE_AREA) {
1389     actionzone_exit(op);
1390   }
1391 
1392   if (newwin) {
1393     return OPERATOR_FINISHED;
1394   }
1395   return OPERATOR_CANCELLED;
1396 }
1397 
SCREEN_OT_area_dupli(wmOperatorType * ot)1398 static void SCREEN_OT_area_dupli(wmOperatorType *ot)
1399 {
1400   ot->name = "Duplicate Area into New Window";
1401   ot->description = "Duplicate selected area into new window";
1402   ot->idname = "SCREEN_OT_area_dupli";
1403 
1404   ot->invoke = area_dupli_invoke;
1405   ot->poll = ED_operator_areaactive;
1406 }
1407 
1408 /** \} */
1409 
1410 /* -------------------------------------------------------------------- */
1411 /** \name Move Area Edge Operator
1412  * \{ */
1413 
1414 /* operator state vars used:
1415  * x, y             mouse coord near edge
1416  * delta            movement of edge
1417  *
1418  * functions:
1419  *
1420  * init()   set default property values, find edge based on mouse coords, test
1421  * if the edge can be moved, select edges, calculate min and max movement
1422  *
1423  * apply()  apply delta on selection
1424  *
1425  * exit()   cleanup, send notifier
1426  *
1427  * cancel() cancel moving
1428  *
1429  * callbacks:
1430  *
1431  * exec()   execute without any user interaction, based on properties
1432  * call init(), apply(), exit()
1433  *
1434  * invoke() gets called on mouse click near edge
1435  * call init(), add handler
1436  *
1437  * modal()  accept modal events while doing it
1438  * call apply() with delta motion
1439  * call exit() and remove handler
1440  */
1441 
1442 typedef struct sAreaMoveData {
1443   int bigger, smaller, origval, step;
1444   char dir;
1445   enum AreaMoveSnapType {
1446     /* Snapping disabled */
1447     SNAP_NONE = 0,
1448     /* Snap to an invisible grid with a unit defined in AREAGRID */
1449     SNAP_AREAGRID,
1450     /* Snap to fraction (half, third.. etc) and adjacent edges. */
1451     SNAP_FRACTION_AND_ADJACENT,
1452     /* Snap to either bigger or smaller, nothing in-between (used for
1453      * global areas). This has priority over other snap types, if it is
1454      * used, toggling SNAP_FRACTION_AND_ADJACENT doesn't work. */
1455     SNAP_BIGGER_SMALLER_ONLY,
1456   } snap_type;
1457 } sAreaMoveData;
1458 
1459 /* helper call to move area-edge, sets limits
1460  * need window bounds in order to get correct limits */
area_move_set_limits(wmWindow * win,bScreen * screen,int dir,int * bigger,int * smaller,bool * use_bigger_smaller_snap)1461 static void area_move_set_limits(wmWindow *win,
1462                                  bScreen *screen,
1463                                  int dir,
1464                                  int *bigger,
1465                                  int *smaller,
1466                                  bool *use_bigger_smaller_snap)
1467 {
1468   /* we check all areas and test for free space with MINSIZE */
1469   *bigger = *smaller = 100000;
1470 
1471   if (use_bigger_smaller_snap != NULL) {
1472     *use_bigger_smaller_snap = false;
1473     LISTBASE_FOREACH (ScrArea *, area, &win->global_areas.areabase) {
1474       int size_min = ED_area_global_min_size_y(area) - 1;
1475       int size_max = ED_area_global_max_size_y(area) - 1;
1476 
1477       size_min = max_ii(size_min, 0);
1478       BLI_assert(size_min <= size_max);
1479 
1480       /* logic here is only tested for lower edge :) */
1481       /* left edge */
1482       if ((area->v1->editflag && area->v2->editflag)) {
1483         *smaller = area->v4->vec.x - size_max;
1484         *bigger = area->v4->vec.x - size_min;
1485         *use_bigger_smaller_snap = true;
1486         return;
1487       }
1488       /* top edge */
1489       if ((area->v2->editflag && area->v3->editflag)) {
1490         *smaller = area->v1->vec.y + size_min;
1491         *bigger = area->v1->vec.y + size_max;
1492         *use_bigger_smaller_snap = true;
1493         return;
1494       }
1495       /* right edge */
1496       if ((area->v3->editflag && area->v4->editflag)) {
1497         *smaller = area->v1->vec.x + size_min;
1498         *bigger = area->v1->vec.x + size_max;
1499         *use_bigger_smaller_snap = true;
1500         return;
1501       }
1502       /* lower edge */
1503       if ((area->v4->editflag && area->v1->editflag)) {
1504         *smaller = area->v2->vec.y - size_max;
1505         *bigger = area->v2->vec.y - size_min;
1506         *use_bigger_smaller_snap = true;
1507         return;
1508       }
1509     }
1510   }
1511 
1512   rcti window_rect;
1513   WM_window_rect_calc(win, &window_rect);
1514 
1515   LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
1516     if (dir == 'h') {
1517       int areamin = ED_area_headersize();
1518 
1519       if (area->v1->vec.y > window_rect.ymin) {
1520         areamin += U.pixelsize;
1521       }
1522       if (area->v2->vec.y < (window_rect.ymax - 1)) {
1523         areamin += U.pixelsize;
1524       }
1525 
1526       int y1 = screen_geom_area_height(area) - areamin;
1527 
1528       /* if top or down edge selected, test height */
1529       if (area->v1->editflag && area->v4->editflag) {
1530         *bigger = min_ii(*bigger, y1);
1531       }
1532       else if (area->v2->editflag && area->v3->editflag) {
1533         *smaller = min_ii(*smaller, y1);
1534       }
1535     }
1536     else {
1537       int areamin = AREAMINX;
1538 
1539       if (area->v1->vec.x > window_rect.xmin) {
1540         areamin += U.pixelsize;
1541       }
1542       if (area->v4->vec.x < (window_rect.xmax - 1)) {
1543         areamin += U.pixelsize;
1544       }
1545 
1546       int x1 = screen_geom_area_width(area) - areamin;
1547 
1548       /* if left or right edge selected, test width */
1549       if (area->v1->editflag && area->v2->editflag) {
1550         *bigger = min_ii(*bigger, x1);
1551       }
1552       else if (area->v3->editflag && area->v4->editflag) {
1553         *smaller = min_ii(*smaller, x1);
1554       }
1555     }
1556   }
1557 }
1558 
1559 /* validate selection inside screen, set variables OK */
1560 /* return false: init failed */
area_move_init(bContext * C,wmOperator * op)1561 static bool area_move_init(bContext *C, wmOperator *op)
1562 {
1563   bScreen *screen = CTX_wm_screen(C);
1564   wmWindow *win = CTX_wm_window(C);
1565 
1566   /* required properties */
1567   int x = RNA_int_get(op->ptr, "x");
1568   int y = RNA_int_get(op->ptr, "y");
1569 
1570   /* setup */
1571   ScrEdge *actedge = screen_geom_find_active_scredge(win, screen, x, y);
1572   if (actedge == NULL) {
1573     return false;
1574   }
1575 
1576   sAreaMoveData *md = MEM_callocN(sizeof(sAreaMoveData), "sAreaMoveData");
1577   op->customdata = md;
1578 
1579   md->dir = screen_geom_edge_is_horizontal(actedge) ? 'h' : 'v';
1580   if (md->dir == 'h') {
1581     md->origval = actedge->v1->vec.y;
1582   }
1583   else {
1584     md->origval = actedge->v1->vec.x;
1585   }
1586 
1587   screen_geom_select_connected_edge(win, actedge);
1588   /* now all vertices with 'flag == 1' are the ones that can be moved. Move this to editflag */
1589   ED_screen_verts_iter(win, screen, v1)
1590   {
1591     v1->editflag = v1->flag;
1592   }
1593 
1594   bool use_bigger_smaller_snap = false;
1595   area_move_set_limits(win, screen, md->dir, &md->bigger, &md->smaller, &use_bigger_smaller_snap);
1596 
1597   md->snap_type = use_bigger_smaller_snap ? SNAP_BIGGER_SMALLER_ONLY : SNAP_AREAGRID;
1598 
1599   return true;
1600 }
1601 
area_snap_calc_location(const bScreen * screen,const enum AreaMoveSnapType snap_type,const int delta,const int origval,const int dir,const int bigger,const int smaller)1602 static int area_snap_calc_location(const bScreen *screen,
1603                                    const enum AreaMoveSnapType snap_type,
1604                                    const int delta,
1605                                    const int origval,
1606                                    const int dir,
1607                                    const int bigger,
1608                                    const int smaller)
1609 {
1610   BLI_assert(snap_type != SNAP_NONE);
1611   int m_cursor_final = -1;
1612   const int m_cursor = origval + delta;
1613   const int m_span = (float)(bigger + smaller);
1614   const int m_min = origval - smaller;
1615   // const int axis_max = axis_min + m_span;
1616 
1617   switch (snap_type) {
1618     case SNAP_AREAGRID:
1619       m_cursor_final = m_cursor;
1620       if (delta != bigger && delta != -smaller) {
1621         m_cursor_final -= (m_cursor % AREAGRID);
1622         CLAMP(m_cursor_final, origval - smaller, origval + bigger);
1623       }
1624       break;
1625 
1626     case SNAP_BIGGER_SMALLER_ONLY:
1627       m_cursor_final = (m_cursor >= bigger) ? bigger : smaller;
1628       break;
1629 
1630     case SNAP_FRACTION_AND_ADJACENT: {
1631       const int axis = (dir == 'v') ? 0 : 1;
1632       int snap_dist_best = INT_MAX;
1633       {
1634         const float div_array[] = {
1635             /* Middle. */
1636             1.0f / 2.0f,
1637             /* Thirds. */
1638             1.0f / 3.0f,
1639             2.0f / 3.0f,
1640             /* Quaters. */
1641             1.0f / 4.0f,
1642             3.0f / 4.0f,
1643             /* Eighth. */
1644             1.0f / 8.0f,
1645             3.0f / 8.0f,
1646             5.0f / 8.0f,
1647             7.0f / 8.0f,
1648         };
1649         /* Test the snap to the best division. */
1650         for (int i = 0; i < ARRAY_SIZE(div_array); i++) {
1651           const int m_cursor_test = m_min + round_fl_to_int(m_span * div_array[i]);
1652           const int snap_dist_test = abs(m_cursor - m_cursor_test);
1653           if (snap_dist_best >= snap_dist_test) {
1654             snap_dist_best = snap_dist_test;
1655             m_cursor_final = m_cursor_test;
1656           }
1657         }
1658       }
1659 
1660       LISTBASE_FOREACH (const ScrVert *, v1, &screen->vertbase) {
1661         if (!v1->editflag) {
1662           continue;
1663         }
1664         const int v_loc = (&v1->vec.x)[!axis];
1665 
1666         LISTBASE_FOREACH (const ScrVert *, v2, &screen->vertbase) {
1667           if (v2->editflag) {
1668             continue;
1669           }
1670           if (v_loc == (&v2->vec.x)[!axis]) {
1671             const int v_loc2 = (&v2->vec.x)[axis];
1672             /* Do not snap to the vertices at the ends. */
1673             if ((origval - smaller) < v_loc2 && v_loc2 < (origval + bigger)) {
1674               const int snap_dist_test = abs(m_cursor - v_loc2);
1675               if (snap_dist_best >= snap_dist_test) {
1676                 snap_dist_best = snap_dist_test;
1677                 m_cursor_final = v_loc2;
1678               }
1679             }
1680           }
1681         }
1682       }
1683       break;
1684     }
1685     case SNAP_NONE:
1686       break;
1687   }
1688 
1689   BLI_assert(ELEM(snap_type, SNAP_BIGGER_SMALLER_ONLY) ||
1690              IN_RANGE_INCL(m_cursor_final, origval - smaller, origval + bigger));
1691 
1692   return m_cursor_final;
1693 }
1694 
1695 /* moves selected screen edge amount of delta, used by split & move */
area_move_apply_do(const bContext * C,int delta,const int origval,const int dir,const int bigger,const int smaller,const enum AreaMoveSnapType snap_type)1696 static void area_move_apply_do(const bContext *C,
1697                                int delta,
1698                                const int origval,
1699                                const int dir,
1700                                const int bigger,
1701                                const int smaller,
1702                                const enum AreaMoveSnapType snap_type)
1703 {
1704   wmWindow *win = CTX_wm_window(C);
1705   bScreen *screen = CTX_wm_screen(C);
1706   short final_loc = -1;
1707   bool doredraw = false;
1708 
1709   if (snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1710     CLAMP(delta, -smaller, bigger);
1711   }
1712 
1713   if (snap_type == SNAP_NONE) {
1714     final_loc = origval + delta;
1715   }
1716   else {
1717     final_loc = area_snap_calc_location(screen, snap_type, delta, origval, dir, bigger, smaller);
1718   }
1719 
1720   BLI_assert(final_loc != -1);
1721   short axis = (dir == 'v') ? 0 : 1;
1722 
1723   ED_screen_verts_iter(win, screen, v1)
1724   {
1725     if (v1->editflag) {
1726       short oldval = (&v1->vec.x)[axis];
1727       (&v1->vec.x)[axis] = final_loc;
1728 
1729       if (oldval == final_loc) {
1730         /* nothing will change to the other vertices either. */
1731         break;
1732       }
1733       doredraw = true;
1734     }
1735   }
1736 
1737   /* only redraw if we actually moved a screen vert, for AREAGRID */
1738   if (doredraw) {
1739     bool redraw_all = false;
1740     ED_screen_areas_iter (win, screen, area) {
1741       if (area->v1->editflag || area->v2->editflag || area->v3->editflag || area->v4->editflag) {
1742         if (ED_area_is_global(area)) {
1743           /* Snap to minimum or maximum for global areas. */
1744           int height = round_fl_to_int(screen_geom_area_height(area) / UI_DPI_FAC);
1745           if (abs(height - area->global->size_min) < abs(height - area->global->size_max)) {
1746             area->global->cur_fixed_height = area->global->size_min;
1747           }
1748           else {
1749             area->global->cur_fixed_height = area->global->size_max;
1750           }
1751 
1752           screen->do_refresh = true;
1753           redraw_all = true;
1754         }
1755         ED_area_tag_redraw_no_rebuild(area);
1756       }
1757     }
1758     if (redraw_all) {
1759       ED_screen_areas_iter (win, screen, area) {
1760         ED_area_tag_redraw(area);
1761       }
1762     }
1763 
1764     ED_screen_global_areas_sync(win);
1765 
1766     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); /* redraw everything */
1767     /* Update preview thumbnail */
1768     BKE_icon_changed(screen->id.icon_id);
1769   }
1770 }
1771 
area_move_apply(bContext * C,wmOperator * op)1772 static void area_move_apply(bContext *C, wmOperator *op)
1773 {
1774   sAreaMoveData *md = op->customdata;
1775   int delta = RNA_int_get(op->ptr, "delta");
1776 
1777   area_move_apply_do(C, delta, md->origval, md->dir, md->bigger, md->smaller, md->snap_type);
1778 }
1779 
area_move_exit(bContext * C,wmOperator * op)1780 static void area_move_exit(bContext *C, wmOperator *op)
1781 {
1782   if (op->customdata) {
1783     MEM_freeN(op->customdata);
1784   }
1785   op->customdata = NULL;
1786 
1787   /* this makes sure aligned edges will result in aligned grabbing */
1788   BKE_screen_remove_double_scrverts(CTX_wm_screen(C));
1789   BKE_screen_remove_double_scredges(CTX_wm_screen(C));
1790 
1791   G.moving &= ~G_TRANSFORM_WM;
1792 }
1793 
area_move_exec(bContext * C,wmOperator * op)1794 static int area_move_exec(bContext *C, wmOperator *op)
1795 {
1796   if (!area_move_init(C, op)) {
1797     return OPERATOR_CANCELLED;
1798   }
1799 
1800   area_move_apply(C, op);
1801   area_move_exit(C, op);
1802 
1803   return OPERATOR_FINISHED;
1804 }
1805 
1806 /* interaction callback */
area_move_invoke(bContext * C,wmOperator * op,const wmEvent * event)1807 static int area_move_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1808 {
1809   RNA_int_set(op->ptr, "x", event->x);
1810   RNA_int_set(op->ptr, "y", event->y);
1811 
1812   if (!area_move_init(C, op)) {
1813     return OPERATOR_PASS_THROUGH;
1814   }
1815 
1816   /* add temp handler */
1817   G.moving |= G_TRANSFORM_WM;
1818   WM_event_add_modal_handler(C, op);
1819 
1820   return OPERATOR_RUNNING_MODAL;
1821 }
1822 
area_move_cancel(bContext * C,wmOperator * op)1823 static void area_move_cancel(bContext *C, wmOperator *op)
1824 {
1825 
1826   RNA_int_set(op->ptr, "delta", 0);
1827   area_move_apply(C, op);
1828   area_move_exit(C, op);
1829 }
1830 
1831 /* modal callback for while moving edges */
area_move_modal(bContext * C,wmOperator * op,const wmEvent * event)1832 static int area_move_modal(bContext *C, wmOperator *op, const wmEvent *event)
1833 {
1834   sAreaMoveData *md = op->customdata;
1835 
1836   /* execute the events */
1837   switch (event->type) {
1838     case MOUSEMOVE: {
1839       int x = RNA_int_get(op->ptr, "x");
1840       int y = RNA_int_get(op->ptr, "y");
1841 
1842       int delta = (md->dir == 'v') ? event->x - x : event->y - y;
1843       RNA_int_set(op->ptr, "delta", delta);
1844 
1845       area_move_apply(C, op);
1846       break;
1847     }
1848     case EVT_MODAL_MAP: {
1849       switch (event->val) {
1850         case KM_MODAL_APPLY:
1851           area_move_exit(C, op);
1852           return OPERATOR_FINISHED;
1853 
1854         case KM_MODAL_CANCEL:
1855           area_move_cancel(C, op);
1856           return OPERATOR_CANCELLED;
1857 
1858         case KM_MODAL_SNAP_ON:
1859           if (md->snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1860             md->snap_type = SNAP_FRACTION_AND_ADJACENT;
1861           }
1862           break;
1863 
1864         case KM_MODAL_SNAP_OFF:
1865           if (md->snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1866             md->snap_type = SNAP_AREAGRID;
1867           }
1868           break;
1869       }
1870       break;
1871     }
1872   }
1873 
1874   return OPERATOR_RUNNING_MODAL;
1875 }
1876 
SCREEN_OT_area_move(wmOperatorType * ot)1877 static void SCREEN_OT_area_move(wmOperatorType *ot)
1878 {
1879   /* identifiers */
1880   ot->name = "Move Area Edges";
1881   ot->description = "Move selected area edges";
1882   ot->idname = "SCREEN_OT_area_move";
1883 
1884   ot->exec = area_move_exec;
1885   ot->invoke = area_move_invoke;
1886   ot->cancel = area_move_cancel;
1887   ot->modal = area_move_modal;
1888   ot->poll = ED_operator_screen_mainwinactive; /* when mouse is over area-edge */
1889 
1890   /* flags */
1891   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
1892 
1893   /* rna */
1894   RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
1895   RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
1896   RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
1897 }
1898 
1899 /** \} */
1900 
1901 /* -------------------------------------------------------------------- */
1902 /** \name Split Area Operator
1903  * \{ */
1904 
1905 /*
1906  * operator state vars:
1907  * fac              spit point
1908  * dir              direction 'v' or 'h'
1909  *
1910  * operator customdata:
1911  * area             pointer to (active) area
1912  * x, y             last used mouse pos
1913  * (more, see below)
1914  *
1915  * functions:
1916  *
1917  * init()   set default property values, find area based on context
1918  *
1919  * apply()  split area based on state vars
1920  *
1921  * exit()   cleanup, send notifier
1922  *
1923  * cancel() remove duplicated area
1924  *
1925  * callbacks:
1926  *
1927  * exec()   execute without any user interaction, based on state vars
1928  * call init(), apply(), exit()
1929  *
1930  * invoke() gets called on mouse click in action-widget
1931  * call init(), add modal handler
1932  * call apply() with initial motion
1933  *
1934  * modal()  accept modal events while doing it
1935  * call move-areas code with delta motion
1936  * call exit() or cancel() and remove handler
1937  */
1938 
1939 typedef struct sAreaSplitData {
1940   int origval;           /* for move areas */
1941   int bigger, smaller;   /* constraints for moving new edge */
1942   int delta;             /* delta move edge */
1943   int origmin, origsize; /* to calculate fac, for property storage */
1944   int previewmode;       /* draw previewline, then split */
1945   void *draw_callback;   /* call `ED_screen_draw_split_preview` */
1946   bool do_snap;
1947 
1948   ScrEdge *nedge; /* new edge */
1949   ScrArea *sarea; /* start area */
1950   ScrArea *narea; /* new area */
1951 
1952 } sAreaSplitData;
1953 
area_split_draw_cb(const struct wmWindow * UNUSED (win),void * userdata)1954 static void area_split_draw_cb(const struct wmWindow *UNUSED(win), void *userdata)
1955 {
1956   const wmOperator *op = userdata;
1957 
1958   sAreaSplitData *sd = op->customdata;
1959   if (sd->sarea) {
1960     int dir = RNA_enum_get(op->ptr, "direction");
1961     float fac = RNA_float_get(op->ptr, "factor");
1962 
1963     ED_screen_draw_split_preview(sd->sarea, dir, fac);
1964   }
1965 }
1966 
1967 /* generic init, menu case, doesn't need active area */
area_split_menu_init(bContext * C,wmOperator * op)1968 static bool area_split_menu_init(bContext *C, wmOperator *op)
1969 {
1970   /* custom data */
1971   sAreaSplitData *sd = (sAreaSplitData *)MEM_callocN(sizeof(sAreaSplitData), "op_area_split");
1972   op->customdata = sd;
1973 
1974   sd->sarea = CTX_wm_area(C);
1975 
1976   return true;
1977 }
1978 
1979 /* generic init, no UI stuff here, assumes active area */
area_split_init(bContext * C,wmOperator * op)1980 static bool area_split_init(bContext *C, wmOperator *op)
1981 {
1982   ScrArea *area = CTX_wm_area(C);
1983 
1984   /* required context */
1985   if (area == NULL) {
1986     return false;
1987   }
1988 
1989   /* required properties */
1990   int dir = RNA_enum_get(op->ptr, "direction");
1991 
1992   /* minimal size */
1993   if (dir == 'v' && area->winx < 2 * AREAMINX) {
1994     return false;
1995   }
1996   if (dir == 'h' && area->winy < 2 * ED_area_headersize()) {
1997     return false;
1998   }
1999 
2000   /* custom data */
2001   sAreaSplitData *sd = (sAreaSplitData *)MEM_callocN(sizeof(sAreaSplitData), "op_area_split");
2002   op->customdata = sd;
2003 
2004   sd->sarea = area;
2005   if (dir == 'v') {
2006     sd->origmin = area->v1->vec.x;
2007     sd->origsize = area->v4->vec.x - sd->origmin;
2008   }
2009   else {
2010     sd->origmin = area->v1->vec.y;
2011     sd->origsize = area->v2->vec.y - sd->origmin;
2012   }
2013 
2014   return true;
2015 }
2016 
2017 /* with area as center, sb is located at: 0=W, 1=N, 2=E, 3=S */
2018 /* used with split operator */
area_findsharededge(bScreen * screen,ScrArea * area,ScrArea * sb)2019 static ScrEdge *area_findsharededge(bScreen *screen, ScrArea *area, ScrArea *sb)
2020 {
2021   ScrVert *sav1 = area->v1;
2022   ScrVert *sav2 = area->v2;
2023   ScrVert *sav3 = area->v3;
2024   ScrVert *sav4 = area->v4;
2025   ScrVert *sbv1 = sb->v1;
2026   ScrVert *sbv2 = sb->v2;
2027   ScrVert *sbv3 = sb->v3;
2028   ScrVert *sbv4 = sb->v4;
2029 
2030   if (sav1 == sbv4 && sav2 == sbv3) { /* area to right of sb = W */
2031     return BKE_screen_find_edge(screen, sav1, sav2);
2032   }
2033   if (sav2 == sbv1 && sav3 == sbv4) { /* area to bottom of sb = N */
2034     return BKE_screen_find_edge(screen, sav2, sav3);
2035   }
2036   if (sav3 == sbv2 && sav4 == sbv1) { /* area to left of sb = E */
2037     return BKE_screen_find_edge(screen, sav3, sav4);
2038   }
2039   if (sav1 == sbv2 && sav4 == sbv3) { /* area on top of sb = S*/
2040     return BKE_screen_find_edge(screen, sav1, sav4);
2041   }
2042 
2043   return NULL;
2044 }
2045 
2046 /* do the split, return success */
area_split_apply(bContext * C,wmOperator * op)2047 static bool area_split_apply(bContext *C, wmOperator *op)
2048 {
2049   const wmWindow *win = CTX_wm_window(C);
2050   bScreen *screen = CTX_wm_screen(C);
2051   sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2052 
2053   float fac = RNA_float_get(op->ptr, "factor");
2054   int dir = RNA_enum_get(op->ptr, "direction");
2055 
2056   sd->narea = area_split(win, screen, sd->sarea, dir, fac, 0); /* 0 = no merge */
2057 
2058   if (sd->narea == NULL) {
2059     return false;
2060   }
2061 
2062   sd->nedge = area_findsharededge(screen, sd->sarea, sd->narea);
2063 
2064   /* select newly created edge, prepare for moving edge */
2065   ED_screen_verts_iter(win, screen, sv)
2066   {
2067     sv->editflag = 0;
2068   }
2069 
2070   sd->nedge->v1->editflag = 1;
2071   sd->nedge->v2->editflag = 1;
2072 
2073   if (dir == 'h') {
2074     sd->origval = sd->nedge->v1->vec.y;
2075   }
2076   else {
2077     sd->origval = sd->nedge->v1->vec.x;
2078   }
2079 
2080   ED_area_tag_redraw(sd->sarea);
2081   ED_area_tag_redraw(sd->narea);
2082 
2083   WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2084   /* Update preview thumbnail */
2085   BKE_icon_changed(screen->id.icon_id);
2086 
2087   return true;
2088 }
2089 
area_split_exit(bContext * C,wmOperator * op)2090 static void area_split_exit(bContext *C, wmOperator *op)
2091 {
2092   if (op->customdata) {
2093     sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2094     if (sd->sarea) {
2095       ED_area_tag_redraw(sd->sarea);
2096     }
2097     if (sd->narea) {
2098       ED_area_tag_redraw(sd->narea);
2099     }
2100 
2101     if (sd->draw_callback) {
2102       WM_draw_cb_exit(CTX_wm_window(C), sd->draw_callback);
2103     }
2104 
2105     MEM_freeN(op->customdata);
2106     op->customdata = NULL;
2107   }
2108 
2109   WM_cursor_modal_restore(CTX_wm_window(C));
2110   WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2111 
2112   /* this makes sure aligned edges will result in aligned grabbing */
2113   BKE_screen_remove_double_scrverts(CTX_wm_screen(C));
2114   BKE_screen_remove_double_scredges(CTX_wm_screen(C));
2115 
2116   G.moving &= ~G_TRANSFORM_WM;
2117 }
2118 
area_split_preview_update_cursor(bContext * C,wmOperator * op)2119 static void area_split_preview_update_cursor(bContext *C, wmOperator *op)
2120 {
2121   wmWindow *win = CTX_wm_window(C);
2122   int dir = RNA_enum_get(op->ptr, "direction");
2123   WM_cursor_set(win, dir == 'h' ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT);
2124 }
2125 
2126 /* UI callback, adds new handler */
area_split_invoke(bContext * C,wmOperator * op,const wmEvent * event)2127 static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2128 {
2129   wmWindow *win = CTX_wm_window(C);
2130   bScreen *screen = CTX_wm_screen(C);
2131 
2132   /* no full window splitting allowed */
2133   BLI_assert(screen->state == SCREENNORMAL);
2134 
2135   PropertyRNA *prop_dir = RNA_struct_find_property(op->ptr, "direction");
2136   PropertyRNA *prop_factor = RNA_struct_find_property(op->ptr, "factor");
2137   PropertyRNA *prop_cursor = RNA_struct_find_property(op->ptr, "cursor");
2138 
2139   int dir;
2140   if (event->type == EVT_ACTIONZONE_AREA) {
2141     sActionzoneData *sad = event->customdata;
2142 
2143     if (sad == NULL || sad->modifier > 0) {
2144       return OPERATOR_PASS_THROUGH;
2145     }
2146 
2147     /* verify *sad itself */
2148     if (sad->sa1 == NULL || sad->az == NULL) {
2149       return OPERATOR_PASS_THROUGH;
2150     }
2151 
2152     /* is this our *sad? if areas not equal it should be passed on */
2153     if (CTX_wm_area(C) != sad->sa1 || sad->sa1 != sad->sa2) {
2154       return OPERATOR_PASS_THROUGH;
2155     }
2156 
2157     /* The factor will be close to 1.0f when near the top-left and the bottom-right corners. */
2158     const float factor_v = ((float)(event->y - sad->sa1->v1->vec.y)) / (float)sad->sa1->winy;
2159     const float factor_h = ((float)(event->x - sad->sa1->v1->vec.x)) / (float)sad->sa1->winx;
2160     const bool is_left = factor_v < 0.5f;
2161     const bool is_bottom = factor_h < 0.5f;
2162     const bool is_right = !is_left;
2163     const bool is_top = !is_bottom;
2164     float factor;
2165 
2166     /* Prepare operator state vars. */
2167     if (ELEM(sad->gesture_dir, 'n', 's')) {
2168       dir = 'h';
2169       factor = factor_h;
2170     }
2171     else {
2172       dir = 'v';
2173       factor = factor_v;
2174     }
2175 
2176     if ((is_top && is_left) || (is_bottom && is_right)) {
2177       factor = 1.0f - factor;
2178     }
2179 
2180     RNA_property_float_set(op->ptr, prop_factor, factor);
2181 
2182     RNA_property_enum_set(op->ptr, prop_dir, dir);
2183 
2184     /* general init, also non-UI case, adds customdata, sets area and defaults */
2185     if (!area_split_init(C, op)) {
2186       return OPERATOR_PASS_THROUGH;
2187     }
2188   }
2189   else if (RNA_property_is_set(op->ptr, prop_dir)) {
2190     ScrArea *area = CTX_wm_area(C);
2191     if (area == NULL) {
2192       return OPERATOR_CANCELLED;
2193     }
2194     dir = RNA_property_enum_get(op->ptr, prop_dir);
2195     if (dir == 'h') {
2196       RNA_property_float_set(
2197           op->ptr, prop_factor, ((float)(event->x - area->v1->vec.x)) / (float)area->winx);
2198     }
2199     else {
2200       RNA_property_float_set(
2201           op->ptr, prop_factor, ((float)(event->y - area->v1->vec.y)) / (float)area->winy);
2202     }
2203 
2204     if (!area_split_init(C, op)) {
2205       return OPERATOR_CANCELLED;
2206     }
2207   }
2208   else {
2209     int event_co[2];
2210 
2211     /* retrieve initial mouse coord, so we can find the active edge */
2212     if (RNA_property_is_set(op->ptr, prop_cursor)) {
2213       RNA_property_int_get_array(op->ptr, prop_cursor, event_co);
2214     }
2215     else {
2216       copy_v2_v2_int(event_co, &event->x);
2217     }
2218 
2219     rcti window_rect;
2220     WM_window_rect_calc(win, &window_rect);
2221 
2222     ScrEdge *actedge = screen_geom_area_map_find_active_scredge(
2223         AREAMAP_FROM_SCREEN(screen), &window_rect, event_co[0], event_co[1]);
2224     if (actedge == NULL) {
2225       return OPERATOR_CANCELLED;
2226     }
2227 
2228     dir = screen_geom_edge_is_horizontal(actedge) ? 'v' : 'h';
2229 
2230     RNA_property_enum_set(op->ptr, prop_dir, dir);
2231 
2232     /* special case, adds customdata, sets defaults */
2233     if (!area_split_menu_init(C, op)) {
2234       return OPERATOR_CANCELLED;
2235     }
2236   }
2237 
2238   sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2239 
2240   if (event->type == EVT_ACTIONZONE_AREA) {
2241     /* do the split */
2242     if (area_split_apply(C, op)) {
2243       area_move_set_limits(win, screen, dir, &sd->bigger, &sd->smaller, NULL);
2244 
2245       /* add temp handler for edge move or cancel */
2246       G.moving |= G_TRANSFORM_WM;
2247       WM_event_add_modal_handler(C, op);
2248 
2249       return OPERATOR_RUNNING_MODAL;
2250     }
2251   }
2252   else {
2253     sd->previewmode = 1;
2254     sd->draw_callback = WM_draw_cb_activate(win, area_split_draw_cb, op);
2255     /* add temp handler for edge move or cancel */
2256     WM_event_add_modal_handler(C, op);
2257     area_split_preview_update_cursor(C, op);
2258 
2259     return OPERATOR_RUNNING_MODAL;
2260   }
2261 
2262   return OPERATOR_PASS_THROUGH;
2263 }
2264 
2265 /* function to be called outside UI context, or for redo */
area_split_exec(bContext * C,wmOperator * op)2266 static int area_split_exec(bContext *C, wmOperator *op)
2267 {
2268   if (!area_split_init(C, op)) {
2269     return OPERATOR_CANCELLED;
2270   }
2271 
2272   area_split_apply(C, op);
2273   area_split_exit(C, op);
2274 
2275   return OPERATOR_FINISHED;
2276 }
2277 
area_split_cancel(bContext * C,wmOperator * op)2278 static void area_split_cancel(bContext *C, wmOperator *op)
2279 {
2280   sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2281 
2282   if (sd->previewmode) {
2283     /* pass */
2284   }
2285   else {
2286     if (screen_area_join(C, CTX_wm_screen(C), sd->sarea, sd->narea)) {
2287       if (CTX_wm_area(C) == sd->narea) {
2288         CTX_wm_area_set(C, NULL);
2289         CTX_wm_region_set(C, NULL);
2290       }
2291       sd->narea = NULL;
2292     }
2293   }
2294   area_split_exit(C, op);
2295 }
2296 
area_split_modal(bContext * C,wmOperator * op,const wmEvent * event)2297 static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event)
2298 {
2299   sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2300   PropertyRNA *prop_dir = RNA_struct_find_property(op->ptr, "direction");
2301   bool update_factor = false;
2302 
2303   /* execute the events */
2304   switch (event->type) {
2305     case MOUSEMOVE:
2306       update_factor = true;
2307       break;
2308 
2309     case LEFTMOUSE:
2310       if (sd->previewmode) {
2311         area_split_apply(C, op);
2312         area_split_exit(C, op);
2313         return OPERATOR_FINISHED;
2314       }
2315       else {
2316         if (event->val == KM_RELEASE) { /* mouse up */
2317           area_split_exit(C, op);
2318           return OPERATOR_FINISHED;
2319         }
2320       }
2321       break;
2322 
2323     case MIDDLEMOUSE:
2324     case EVT_TABKEY:
2325       if (sd->previewmode == 0) {
2326         /* pass */
2327       }
2328       else {
2329         if (event->val == KM_PRESS) {
2330           if (sd->sarea) {
2331             int dir = RNA_property_enum_get(op->ptr, prop_dir);
2332             RNA_property_enum_set(op->ptr, prop_dir, (dir == 'v') ? 'h' : 'v');
2333             area_split_preview_update_cursor(C, op);
2334             update_factor = true;
2335           }
2336         }
2337       }
2338 
2339       break;
2340 
2341     case RIGHTMOUSE: /* cancel operation */
2342     case EVT_ESCKEY:
2343       area_split_cancel(C, op);
2344       return OPERATOR_CANCELLED;
2345 
2346     case EVT_LEFTCTRLKEY:
2347       sd->do_snap = event->val == KM_PRESS;
2348       update_factor = true;
2349       break;
2350   }
2351 
2352   if (update_factor) {
2353     const int dir = RNA_property_enum_get(op->ptr, prop_dir);
2354 
2355     sd->delta = (dir == 'v') ? event->x - sd->origval : event->y - sd->origval;
2356 
2357     if (sd->previewmode == 0) {
2358       if (sd->do_snap) {
2359         const int snap_loc = area_snap_calc_location(CTX_wm_screen(C),
2360                                                      SNAP_FRACTION_AND_ADJACENT,
2361                                                      sd->delta,
2362                                                      sd->origval,
2363                                                      dir,
2364                                                      sd->bigger,
2365                                                      sd->smaller);
2366         sd->delta = snap_loc - sd->origval;
2367       }
2368       area_move_apply_do(C, sd->delta, sd->origval, dir, sd->bigger, sd->smaller, SNAP_NONE);
2369     }
2370     else {
2371       if (sd->sarea) {
2372         ED_area_tag_redraw(sd->sarea);
2373       }
2374       /* area context not set */
2375       sd->sarea = BKE_screen_find_area_xy(CTX_wm_screen(C), SPACE_TYPE_ANY, event->x, event->y);
2376 
2377       if (sd->sarea) {
2378         ScrArea *area = sd->sarea;
2379         if (dir == 'v') {
2380           sd->origmin = area->v1->vec.x;
2381           sd->origsize = area->v4->vec.x - sd->origmin;
2382         }
2383         else {
2384           sd->origmin = area->v1->vec.y;
2385           sd->origsize = area->v2->vec.y - sd->origmin;
2386         }
2387 
2388         if (sd->do_snap) {
2389           area->v1->editflag = area->v2->editflag = area->v3->editflag = area->v4->editflag = 1;
2390 
2391           const int snap_loc = area_snap_calc_location(CTX_wm_screen(C),
2392                                                        SNAP_FRACTION_AND_ADJACENT,
2393                                                        sd->delta,
2394                                                        sd->origval,
2395                                                        dir,
2396                                                        sd->origmin + sd->origsize,
2397                                                        -sd->origmin);
2398 
2399           area->v1->editflag = area->v2->editflag = area->v3->editflag = area->v4->editflag = 0;
2400           sd->delta = snap_loc - sd->origval;
2401         }
2402 
2403         ED_area_tag_redraw(sd->sarea);
2404       }
2405 
2406       CTX_wm_screen(C)->do_draw = true;
2407     }
2408 
2409     float fac = (float)(sd->delta + sd->origval - sd->origmin) / sd->origsize;
2410     RNA_float_set(op->ptr, "factor", fac);
2411   }
2412 
2413   return OPERATOR_RUNNING_MODAL;
2414 }
2415 
2416 static const EnumPropertyItem prop_direction_items[] = {
2417     {'h', "HORIZONTAL", 0, "Horizontal", ""},
2418     {'v', "VERTICAL", 0, "Vertical", ""},
2419     {0, NULL, 0, NULL, NULL},
2420 };
2421 
SCREEN_OT_area_split(wmOperatorType * ot)2422 static void SCREEN_OT_area_split(wmOperatorType *ot)
2423 {
2424   ot->name = "Split Area";
2425   ot->description = "Split selected area into new windows";
2426   ot->idname = "SCREEN_OT_area_split";
2427 
2428   ot->exec = area_split_exec;
2429   ot->invoke = area_split_invoke;
2430   ot->modal = area_split_modal;
2431   ot->cancel = area_split_cancel;
2432 
2433   ot->poll = screen_active_editable;
2434 
2435   /* flags */
2436   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
2437 
2438   /* rna */
2439   RNA_def_enum(ot->srna, "direction", prop_direction_items, 'h', "Direction", "");
2440   RNA_def_float(ot->srna, "factor", 0.5f, 0.0, 1.0, "Factor", "", 0.0, 1.0);
2441   RNA_def_int_vector(
2442       ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX);
2443 }
2444 
2445 /** \} */
2446 
2447 /* -------------------------------------------------------------------- */
2448 /** \name Scale Region Edge Operator
2449  * \{ */
2450 
2451 typedef struct RegionMoveData {
2452   AZone *az;
2453   ARegion *region;
2454   ScrArea *area;
2455   int bigger, smaller, origval;
2456   int origx, origy;
2457   int maxsize;
2458   AZEdge edge;
2459 
2460 } RegionMoveData;
2461 
area_max_regionsize(ScrArea * area,ARegion * scalear,AZEdge edge)2462 static int area_max_regionsize(ScrArea *area, ARegion *scalear, AZEdge edge)
2463 {
2464   int dist;
2465 
2466   /* regions in regions. */
2467   if (scalear->alignment & RGN_SPLIT_PREV) {
2468     const int align = RGN_ALIGN_ENUM_FROM_MASK(scalear->alignment);
2469 
2470     if (ELEM(align, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
2471       ARegion *region = scalear->prev;
2472       dist = region->winy + scalear->winy - U.pixelsize;
2473     }
2474     else /* if (ELEM(align, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) */ {
2475       ARegion *region = scalear->prev;
2476       dist = region->winx + scalear->winx - U.pixelsize;
2477     }
2478   }
2479   else {
2480     if (edge == AE_RIGHT_TO_TOPLEFT || edge == AE_LEFT_TO_TOPRIGHT) {
2481       dist = BLI_rcti_size_x(&area->totrct);
2482     }
2483     else { /* AE_BOTTOM_TO_TOPLEFT, AE_TOP_TO_BOTTOMRIGHT */
2484       dist = BLI_rcti_size_y(&area->totrct);
2485     }
2486 
2487     /* subtractwidth of regions on opposite side
2488      * prevents dragging regions into other opposite regions */
2489     LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
2490       if (region == scalear) {
2491         continue;
2492       }
2493 
2494       if (scalear->alignment == RGN_ALIGN_LEFT && region->alignment == RGN_ALIGN_RIGHT) {
2495         dist -= region->winx;
2496       }
2497       else if (scalear->alignment == RGN_ALIGN_RIGHT && region->alignment == RGN_ALIGN_LEFT) {
2498         dist -= region->winx;
2499       }
2500       else if (scalear->alignment == RGN_ALIGN_TOP &&
2501                (region->alignment == RGN_ALIGN_BOTTOM ||
2502                 ELEM(
2503                     region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) {
2504         dist -= region->winy;
2505       }
2506       else if (scalear->alignment == RGN_ALIGN_BOTTOM &&
2507                (region->alignment == RGN_ALIGN_TOP ||
2508                 ELEM(
2509                     region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) {
2510         dist -= region->winy;
2511       }
2512     }
2513   }
2514 
2515   dist /= UI_DPI_FAC;
2516   return dist;
2517 }
2518 
is_split_edge(const int alignment,const AZEdge edge)2519 static bool is_split_edge(const int alignment, const AZEdge edge)
2520 {
2521   return ((alignment == RGN_ALIGN_BOTTOM) && (edge == AE_TOP_TO_BOTTOMRIGHT)) ||
2522          ((alignment == RGN_ALIGN_TOP) && (edge == AE_BOTTOM_TO_TOPLEFT)) ||
2523          ((alignment == RGN_ALIGN_LEFT) && (edge == AE_RIGHT_TO_TOPLEFT)) ||
2524          ((alignment == RGN_ALIGN_RIGHT) && (edge == AE_LEFT_TO_TOPRIGHT));
2525 }
2526 
region_scale_exit(wmOperator * op)2527 static void region_scale_exit(wmOperator *op)
2528 {
2529   MEM_freeN(op->customdata);
2530   op->customdata = NULL;
2531 
2532   G.moving &= ~G_TRANSFORM_WM;
2533 }
2534 
region_scale_invoke(bContext * C,wmOperator * op,const wmEvent * event)2535 static int region_scale_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2536 {
2537   sActionzoneData *sad = event->customdata;
2538 
2539   if (event->type != EVT_ACTIONZONE_REGION) {
2540     BKE_report(op->reports, RPT_ERROR, "Can only scale region size from an action zone");
2541     return OPERATOR_CANCELLED;
2542   }
2543 
2544   AZone *az = sad->az;
2545 
2546   if (az->region) {
2547     RegionMoveData *rmd = MEM_callocN(sizeof(RegionMoveData), "RegionMoveData");
2548 
2549     op->customdata = rmd;
2550 
2551     rmd->az = az;
2552     /* special case for region within region - this allows the scale of
2553      * the parent region if the azone edge is not the edge splitting
2554      * both regions */
2555     if ((az->region->alignment & RGN_SPLIT_PREV) && az->region->prev &&
2556         !is_split_edge(RGN_ALIGN_ENUM_FROM_MASK(az->region->alignment), az->edge)) {
2557       rmd->region = az->region->prev;
2558     }
2559     else {
2560       rmd->region = az->region;
2561     }
2562     rmd->area = sad->sa1;
2563     rmd->edge = az->edge;
2564     rmd->origx = event->x;
2565     rmd->origy = event->y;
2566     rmd->maxsize = area_max_regionsize(rmd->area, rmd->region, rmd->edge);
2567 
2568     /* if not set we do now, otherwise it uses type */
2569     if (rmd->region->sizex == 0) {
2570       rmd->region->sizex = rmd->region->winx;
2571     }
2572     if (rmd->region->sizey == 0) {
2573       rmd->region->sizey = rmd->region->winy;
2574     }
2575 
2576     /* now copy to regionmovedata */
2577     if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2578       rmd->origval = rmd->region->sizex;
2579     }
2580     else {
2581       rmd->origval = rmd->region->sizey;
2582     }
2583 
2584     CLAMP(rmd->maxsize, 0, 1000);
2585 
2586     /* add temp handler */
2587     G.moving |= G_TRANSFORM_WM;
2588     WM_event_add_modal_handler(C, op);
2589 
2590     return OPERATOR_RUNNING_MODAL;
2591   }
2592 
2593   return OPERATOR_FINISHED;
2594 }
2595 
region_scale_validate_size(RegionMoveData * rmd)2596 static void region_scale_validate_size(RegionMoveData *rmd)
2597 {
2598   if ((rmd->region->flag & RGN_FLAG_HIDDEN) == 0) {
2599     short *size, maxsize = -1;
2600 
2601     if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2602       size = &rmd->region->sizex;
2603     }
2604     else {
2605       size = &rmd->region->sizey;
2606     }
2607 
2608     maxsize = rmd->maxsize - (UI_UNIT_Y / UI_DPI_FAC);
2609 
2610     if (*size > maxsize && maxsize > 0) {
2611       *size = maxsize;
2612     }
2613   }
2614 }
2615 
region_scale_toggle_hidden(bContext * C,RegionMoveData * rmd)2616 static void region_scale_toggle_hidden(bContext *C, RegionMoveData *rmd)
2617 {
2618   /* hidden areas may have bad 'View2D.cur' value,
2619    * correct before displaying. see T45156 */
2620   if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2621     UI_view2d_curRect_validate(&rmd->region->v2d);
2622   }
2623 
2624   region_toggle_hidden(C, rmd->region, 0);
2625   region_scale_validate_size(rmd);
2626 
2627   if ((rmd->region->flag & RGN_FLAG_HIDDEN) == 0) {
2628     if (rmd->region->regiontype == RGN_TYPE_HEADER) {
2629       ARegion *region_tool_header = BKE_area_find_region_type(rmd->area, RGN_TYPE_TOOL_HEADER);
2630       if (region_tool_header != NULL) {
2631         if ((region_tool_header->flag & RGN_FLAG_HIDDEN_BY_USER) == 0 &&
2632             (region_tool_header->flag & RGN_FLAG_HIDDEN) != 0) {
2633           region_toggle_hidden(C, region_tool_header, 0);
2634         }
2635       }
2636     }
2637   }
2638 }
2639 
region_scale_modal(bContext * C,wmOperator * op,const wmEvent * event)2640 static int region_scale_modal(bContext *C, wmOperator *op, const wmEvent *event)
2641 {
2642   RegionMoveData *rmd = op->customdata;
2643   int delta;
2644 
2645   /* execute the events */
2646   switch (event->type) {
2647     case MOUSEMOVE: {
2648       const float aspect = BLI_rctf_size_x(&rmd->region->v2d.cur) /
2649                            (BLI_rcti_size_x(&rmd->region->v2d.mask) + 1);
2650       const int snap_size_threshold = (U.widget_unit * 2) / aspect;
2651       if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2652         delta = event->x - rmd->origx;
2653         if (rmd->edge == AE_LEFT_TO_TOPRIGHT) {
2654           delta = -delta;
2655         }
2656 
2657         /* region sizes now get multiplied */
2658         delta /= UI_DPI_FAC;
2659 
2660         const int size_no_snap = rmd->origval + delta;
2661         rmd->region->sizex = size_no_snap;
2662 
2663         if (rmd->region->type->snap_size) {
2664           short sizex_test = rmd->region->type->snap_size(rmd->region, rmd->region->sizex, 0);
2665           if (abs(rmd->region->sizex - sizex_test) < snap_size_threshold) {
2666             rmd->region->sizex = sizex_test;
2667           }
2668         }
2669         CLAMP(rmd->region->sizex, 0, rmd->maxsize);
2670 
2671         if (size_no_snap < UI_UNIT_X / aspect) {
2672           rmd->region->sizex = rmd->origval;
2673           if (!(rmd->region->flag & RGN_FLAG_HIDDEN)) {
2674             region_scale_toggle_hidden(C, rmd);
2675           }
2676         }
2677         else if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2678           region_scale_toggle_hidden(C, rmd);
2679         }
2680         else if (rmd->region->flag & RGN_FLAG_DYNAMIC_SIZE) {
2681           rmd->region->sizex = rmd->origval;
2682         }
2683       }
2684       else {
2685         delta = event->y - rmd->origy;
2686         if (rmd->edge == AE_BOTTOM_TO_TOPLEFT) {
2687           delta = -delta;
2688         }
2689 
2690         /* region sizes now get multiplied */
2691         delta /= UI_DPI_FAC;
2692 
2693         const int size_no_snap = rmd->origval + delta;
2694         rmd->region->sizey = size_no_snap;
2695 
2696         if (rmd->region->type->snap_size) {
2697           short sizey_test = rmd->region->type->snap_size(rmd->region, rmd->region->sizey, 1);
2698           if (abs(rmd->region->sizey - sizey_test) < snap_size_threshold) {
2699             rmd->region->sizey = sizey_test;
2700           }
2701         }
2702         CLAMP(rmd->region->sizey, 0, rmd->maxsize);
2703 
2704         /* note, 'UI_UNIT_Y/4' means you need to drag the footer and execute region
2705          * almost all the way down for it to become hidden, this is done
2706          * otherwise its too easy to do this by accident */
2707         if (size_no_snap < (UI_UNIT_Y / 4) / aspect) {
2708           rmd->region->sizey = rmd->origval;
2709           if (!(rmd->region->flag & RGN_FLAG_HIDDEN)) {
2710             region_scale_toggle_hidden(C, rmd);
2711           }
2712         }
2713         else if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2714           region_scale_toggle_hidden(C, rmd);
2715         }
2716         else if (rmd->region->flag & RGN_FLAG_DYNAMIC_SIZE) {
2717           rmd->region->sizey = rmd->origval;
2718         }
2719       }
2720       ED_area_tag_redraw(rmd->area);
2721       WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2722 
2723       break;
2724     }
2725     case LEFTMOUSE:
2726       if (event->val == KM_RELEASE) {
2727         if (len_manhattan_v2v2_int(&event->x, &rmd->origx) <= WM_EVENT_CURSOR_MOTION_THRESHOLD) {
2728           if (rmd->region->flag & RGN_FLAG_HIDDEN) {
2729             region_scale_toggle_hidden(C, rmd);
2730           }
2731           else if (rmd->region->flag & RGN_FLAG_TOO_SMALL) {
2732             region_scale_validate_size(rmd);
2733           }
2734 
2735           ED_area_tag_redraw(rmd->area);
2736           WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2737         }
2738 
2739         region_scale_exit(op);
2740 
2741         return OPERATOR_FINISHED;
2742       }
2743       break;
2744 
2745     case EVT_ESCKEY:
2746       break;
2747   }
2748 
2749   return OPERATOR_RUNNING_MODAL;
2750 }
2751 
region_scale_cancel(bContext * UNUSED (C),wmOperator * op)2752 static void region_scale_cancel(bContext *UNUSED(C), wmOperator *op)
2753 {
2754   region_scale_exit(op);
2755 }
2756 
SCREEN_OT_region_scale(wmOperatorType * ot)2757 static void SCREEN_OT_region_scale(wmOperatorType *ot)
2758 {
2759   /* identifiers */
2760   ot->name = "Scale Region Size";
2761   ot->description = "Scale selected area";
2762   ot->idname = "SCREEN_OT_region_scale";
2763 
2764   ot->invoke = region_scale_invoke;
2765   ot->modal = region_scale_modal;
2766   ot->cancel = region_scale_cancel;
2767 
2768   ot->poll = ED_operator_areaactive;
2769 
2770   /* flags */
2771   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
2772 }
2773 
2774 /** \} */
2775 
2776 /* -------------------------------------------------------------------- */
2777 /** \name Frame Change Operator
2778  * \{ */
2779 
areas_do_frame_follow(bContext * C,bool middle)2780 static void areas_do_frame_follow(bContext *C, bool middle)
2781 {
2782   bScreen *screen_ctx = CTX_wm_screen(C);
2783   Scene *scene = CTX_data_scene(C);
2784   wmWindowManager *wm = CTX_wm_manager(C);
2785   LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
2786     const bScreen *screen = WM_window_get_active_screen(window);
2787 
2788     LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
2789       LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
2790         /* do follow here if editor type supports it */
2791         if ((screen_ctx->redraws_flag & TIME_FOLLOW)) {
2792           if ((region->regiontype == RGN_TYPE_WINDOW &&
2793                ELEM(area->spacetype, SPACE_SEQ, SPACE_GRAPH, SPACE_ACTION, SPACE_NLA)) ||
2794               (area->spacetype == SPACE_CLIP && region->regiontype == RGN_TYPE_PREVIEW)) {
2795             float w = BLI_rctf_size_x(&region->v2d.cur);
2796 
2797             if (middle) {
2798               if ((scene->r.cfra < region->v2d.cur.xmin) ||
2799                   (scene->r.cfra > region->v2d.cur.xmax)) {
2800                 region->v2d.cur.xmax = scene->r.cfra + (w / 2);
2801                 region->v2d.cur.xmin = scene->r.cfra - (w / 2);
2802               }
2803             }
2804             else {
2805               if (scene->r.cfra < region->v2d.cur.xmin) {
2806                 region->v2d.cur.xmax = scene->r.cfra;
2807                 region->v2d.cur.xmin = region->v2d.cur.xmax - w;
2808               }
2809               else if (scene->r.cfra > region->v2d.cur.xmax) {
2810                 region->v2d.cur.xmin = scene->r.cfra;
2811                 region->v2d.cur.xmax = region->v2d.cur.xmin + w;
2812               }
2813             }
2814           }
2815         }
2816       }
2817     }
2818   }
2819 }
2820 
2821 /* function to be called outside UI context, or for redo */
frame_offset_exec(bContext * C,wmOperator * op)2822 static int frame_offset_exec(bContext *C, wmOperator *op)
2823 {
2824   Scene *scene = CTX_data_scene(C);
2825 
2826   int delta = RNA_int_get(op->ptr, "delta");
2827 
2828   CFRA += delta;
2829   FRAMENUMBER_MIN_CLAMP(CFRA);
2830   SUBFRA = 0.f;
2831 
2832   areas_do_frame_follow(C, false);
2833 
2834   DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
2835 
2836   WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
2837 
2838   return OPERATOR_FINISHED;
2839 }
2840 
SCREEN_OT_frame_offset(wmOperatorType * ot)2841 static void SCREEN_OT_frame_offset(wmOperatorType *ot)
2842 {
2843   ot->name = "Frame Offset";
2844   ot->idname = "SCREEN_OT_frame_offset";
2845   ot->description = "Move current frame forward/backward by a given number";
2846 
2847   ot->exec = frame_offset_exec;
2848 
2849   ot->poll = ED_operator_screenactive_norender;
2850   ot->flag = OPTYPE_UNDO_GROUPED;
2851   ot->undo_group = "Frame Change";
2852 
2853   /* rna */
2854   RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
2855 }
2856 
2857 /** \} */
2858 
2859 /* -------------------------------------------------------------------- */
2860 /** \name Frame Jump Operator
2861  * \{ */
2862 
2863 /* function to be called outside UI context, or for redo */
frame_jump_exec(bContext * C,wmOperator * op)2864 static int frame_jump_exec(bContext *C, wmOperator *op)
2865 {
2866   Scene *scene = CTX_data_scene(C);
2867   wmTimer *animtimer = CTX_wm_screen(C)->animtimer;
2868 
2869   /* Don't change CFRA directly if animtimer is running as this can cause
2870    * first/last frame not to be actually shown (bad since for example physics
2871    * simulations aren't reset properly).
2872    */
2873   if (animtimer) {
2874     ScreenAnimData *sad = animtimer->customdata;
2875 
2876     sad->flag |= ANIMPLAY_FLAG_USE_NEXT_FRAME;
2877 
2878     if (RNA_boolean_get(op->ptr, "end")) {
2879       sad->nextfra = PEFRA;
2880     }
2881     else {
2882       sad->nextfra = PSFRA;
2883     }
2884   }
2885   else {
2886     if (RNA_boolean_get(op->ptr, "end")) {
2887       CFRA = PEFRA;
2888     }
2889     else {
2890       CFRA = PSFRA;
2891     }
2892 
2893     areas_do_frame_follow(C, true);
2894 
2895     DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
2896 
2897     WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
2898   }
2899 
2900   return OPERATOR_FINISHED;
2901 }
2902 
SCREEN_OT_frame_jump(wmOperatorType * ot)2903 static void SCREEN_OT_frame_jump(wmOperatorType *ot)
2904 {
2905   ot->name = "Jump to Endpoint";
2906   ot->description = "Jump to first/last frame in frame range";
2907   ot->idname = "SCREEN_OT_frame_jump";
2908 
2909   ot->exec = frame_jump_exec;
2910 
2911   ot->poll = ED_operator_screenactive_norender;
2912   ot->flag = OPTYPE_UNDO_GROUPED;
2913   ot->undo_group = "Frame Change";
2914 
2915   /* rna */
2916   RNA_def_boolean(ot->srna, "end", 0, "Last Frame", "Jump to the last frame of the frame range");
2917 }
2918 
2919 /** \} */
2920 
2921 /* -------------------------------------------------------------------- */
2922 /** \name Jump to Key-Frame Operator
2923  * \{ */
2924 
2925 /* function to be called outside UI context, or for redo */
keyframe_jump_exec(bContext * C,wmOperator * op)2926 static int keyframe_jump_exec(bContext *C, wmOperator *op)
2927 {
2928   Scene *scene = CTX_data_scene(C);
2929   Object *ob = CTX_data_active_object(C);
2930   bDopeSheet ads = {NULL};
2931   const bool next = RNA_boolean_get(op->ptr, "next");
2932   bool done = false;
2933 
2934   /* sanity checks */
2935   if (scene == NULL) {
2936     return OPERATOR_CANCELLED;
2937   }
2938 
2939   float cfra = (float)(CFRA);
2940 
2941   /* init binarytree-list for getting keyframes */
2942   DLRBT_Tree keys;
2943   BLI_dlrbTree_init(&keys);
2944 
2945   /* seed up dummy dopesheet context with flags to perform necessary filtering */
2946   if ((scene->flag & SCE_KEYS_NO_SELONLY) == 0) {
2947     /* only selected channels are included */
2948     ads.filterflag |= ADS_FILTER_ONLYSEL;
2949   }
2950 
2951   /* populate tree with keyframe nodes */
2952   scene_to_keylist(&ads, scene, &keys, 0);
2953 
2954   if (ob) {
2955     ob_to_keylist(&ads, ob, &keys, 0);
2956 
2957     if (ob->type == OB_GPENCIL) {
2958       const bool active = !(scene->flag & SCE_KEYS_NO_SELONLY);
2959       gpencil_to_keylist(&ads, ob->data, &keys, active);
2960     }
2961   }
2962 
2963   {
2964     Mask *mask = CTX_data_edit_mask(C);
2965     if (mask) {
2966       MaskLayer *masklay = BKE_mask_layer_active(mask);
2967       mask_to_keylist(&ads, masklay, &keys);
2968     }
2969   }
2970 
2971   /* find matching keyframe in the right direction */
2972   ActKeyColumn *ak;
2973   if (next) {
2974     ak = (ActKeyColumn *)BLI_dlrbTree_search_next(&keys, compare_ak_cfraPtr, &cfra);
2975   }
2976   else {
2977     ak = (ActKeyColumn *)BLI_dlrbTree_search_prev(&keys, compare_ak_cfraPtr, &cfra);
2978   }
2979 
2980   while ((ak != NULL) && (done == false)) {
2981     if (CFRA != (int)ak->cfra) {
2982       /* this changes the frame, so set the frame and we're done */
2983       CFRA = (int)ak->cfra;
2984       done = true;
2985     }
2986     else {
2987       /* take another step... */
2988       if (next) {
2989         ak = ak->next;
2990       }
2991       else {
2992         ak = ak->prev;
2993       }
2994     }
2995   }
2996 
2997   /* free temp stuff */
2998   BLI_dlrbTree_free(&keys);
2999 
3000   /* any success? */
3001   if (done == false) {
3002     BKE_report(op->reports, RPT_INFO, "No more keyframes to jump to in this direction");
3003 
3004     return OPERATOR_CANCELLED;
3005   }
3006 
3007   areas_do_frame_follow(C, true);
3008 
3009   DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
3010 
3011   WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
3012 
3013   return OPERATOR_FINISHED;
3014 }
3015 
SCREEN_OT_keyframe_jump(wmOperatorType * ot)3016 static void SCREEN_OT_keyframe_jump(wmOperatorType *ot)
3017 {
3018   ot->name = "Jump to Keyframe";
3019   ot->description = "Jump to previous/next keyframe";
3020   ot->idname = "SCREEN_OT_keyframe_jump";
3021 
3022   ot->exec = keyframe_jump_exec;
3023 
3024   ot->poll = ED_operator_screenactive_norender;
3025   ot->flag = OPTYPE_UNDO_GROUPED;
3026   ot->undo_group = "Frame Change";
3027 
3028   /* properties */
3029   RNA_def_boolean(ot->srna, "next", true, "Next Keyframe", "");
3030 }
3031 
3032 /** \} */
3033 
3034 /* -------------------------------------------------------------------- */
3035 /** \name Jump to Marker Operator
3036  * \{ */
3037 
3038 /* function to be called outside UI context, or for redo */
marker_jump_exec(bContext * C,wmOperator * op)3039 static int marker_jump_exec(bContext *C, wmOperator *op)
3040 {
3041   Scene *scene = CTX_data_scene(C);
3042   int closest = CFRA;
3043   const bool next = RNA_boolean_get(op->ptr, "next");
3044   bool found = false;
3045 
3046   /* find matching marker in the right direction */
3047   LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
3048     if (next) {
3049       if ((marker->frame > CFRA) && (!found || closest > marker->frame)) {
3050         closest = marker->frame;
3051         found = true;
3052       }
3053     }
3054     else {
3055       if ((marker->frame < CFRA) && (!found || closest < marker->frame)) {
3056         closest = marker->frame;
3057         found = true;
3058       }
3059     }
3060   }
3061 
3062   /* any success? */
3063   if (!found) {
3064     BKE_report(op->reports, RPT_INFO, "No more markers to jump to in this direction");
3065 
3066     return OPERATOR_CANCELLED;
3067   }
3068 
3069   CFRA = closest;
3070 
3071   areas_do_frame_follow(C, true);
3072 
3073   DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
3074 
3075   WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
3076 
3077   return OPERATOR_FINISHED;
3078 }
3079 
SCREEN_OT_marker_jump(wmOperatorType * ot)3080 static void SCREEN_OT_marker_jump(wmOperatorType *ot)
3081 {
3082   ot->name = "Jump to Marker";
3083   ot->description = "Jump to previous/next marker";
3084   ot->idname = "SCREEN_OT_marker_jump";
3085 
3086   ot->exec = marker_jump_exec;
3087 
3088   ot->poll = ED_operator_screenactive_norender;
3089   ot->flag = OPTYPE_UNDO_GROUPED;
3090   ot->undo_group = "Frame Change";
3091 
3092   /* properties */
3093   RNA_def_boolean(ot->srna, "next", true, "Next Marker", "");
3094 }
3095 
3096 /** \} */
3097 
3098 /* -------------------------------------------------------------------- */
3099 /** \name Set Screen Operator
3100  * \{ */
3101 
3102 /* function to be called outside UI context, or for redo */
screen_set_exec(bContext * C,wmOperator * op)3103 static int screen_set_exec(bContext *C, wmOperator *op)
3104 {
3105   WorkSpace *workspace = CTX_wm_workspace(C);
3106   int delta = RNA_int_get(op->ptr, "delta");
3107 
3108   if (ED_workspace_layout_cycle(workspace, delta, C)) {
3109     return OPERATOR_FINISHED;
3110   }
3111 
3112   return OPERATOR_CANCELLED;
3113 }
3114 
SCREEN_OT_screen_set(wmOperatorType * ot)3115 static void SCREEN_OT_screen_set(wmOperatorType *ot)
3116 {
3117   ot->name = "Set Screen";
3118   ot->description = "Cycle through available screens";
3119   ot->idname = "SCREEN_OT_screen_set";
3120 
3121   ot->exec = screen_set_exec;
3122   ot->poll = ED_operator_screenactive;
3123 
3124   /* rna */
3125   RNA_def_int(ot->srna, "delta", 1, -1, 1, "Delta", "", -1, 1);
3126 }
3127 
3128 /** \} */
3129 
3130 /* -------------------------------------------------------------------- */
3131 /** \name Screen Full-Area Operator
3132  * \{ */
3133 
3134 /* function to be called outside UI context, or for redo */
screen_maximize_area_exec(bContext * C,wmOperator * op)3135 static int screen_maximize_area_exec(bContext *C, wmOperator *op)
3136 {
3137   bScreen *screen = CTX_wm_screen(C);
3138   ScrArea *area = NULL;
3139   const bool hide_panels = RNA_boolean_get(op->ptr, "use_hide_panels");
3140 
3141   /* search current screen for 'fullscreen' areas */
3142   /* prevents restoring info header, when mouse is over it */
3143   LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) {
3144     if (area_iter->full) {
3145       area = area_iter;
3146       break;
3147     }
3148   }
3149 
3150   if (area == NULL) {
3151     area = CTX_wm_area(C);
3152   }
3153 
3154   if (hide_panels) {
3155     if (!ELEM(screen->state, SCREENNORMAL, SCREENFULL)) {
3156       return OPERATOR_CANCELLED;
3157     }
3158     ED_screen_state_toggle(C, CTX_wm_window(C), area, SCREENFULL);
3159   }
3160   else {
3161     if (!ELEM(screen->state, SCREENNORMAL, SCREENMAXIMIZED)) {
3162       return OPERATOR_CANCELLED;
3163     }
3164     ED_screen_state_toggle(C, CTX_wm_window(C), area, SCREENMAXIMIZED);
3165   }
3166 
3167   return OPERATOR_FINISHED;
3168 }
3169 
screen_maximize_area_poll(bContext * C)3170 static bool screen_maximize_area_poll(bContext *C)
3171 {
3172   const bScreen *screen = CTX_wm_screen(C);
3173   const ScrArea *area = CTX_wm_area(C);
3174   return ED_operator_areaactive(C) &&
3175          /* Don't allow maximizing global areas but allow minimizing from them. */
3176          ((screen->state != SCREENNORMAL) || !ED_area_is_global(area));
3177 }
3178 
SCREEN_OT_screen_full_area(wmOperatorType * ot)3179 static void SCREEN_OT_screen_full_area(wmOperatorType *ot)
3180 {
3181   PropertyRNA *prop;
3182 
3183   ot->name = "Toggle Maximize Area";
3184   ot->description = "Toggle display selected area as fullscreen/maximized";
3185   ot->idname = "SCREEN_OT_screen_full_area";
3186 
3187   ot->exec = screen_maximize_area_exec;
3188   ot->poll = screen_maximize_area_poll;
3189   ot->flag = 0;
3190 
3191   prop = RNA_def_boolean(ot->srna, "use_hide_panels", false, "Hide Panels", "Hide all the panels");
3192   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3193 }
3194 
3195 /** \} */
3196 
3197 /* -------------------------------------------------------------------- */
3198 /** \name Screen Join-Area Operator
3199  * \{ */
3200 
3201 /* operator state vars used:
3202  * x1, y1     mouse coord in first area, which will disappear
3203  * x2, y2     mouse coord in 2nd area, which will become joined
3204  *
3205  * functions:
3206  *
3207  * init()   find edge based on state vars
3208  * test if the edge divides two areas,
3209  * store active and nonactive area,
3210  *
3211  * apply()  do the actual join
3212  *
3213  * exit()   cleanup, send notifier
3214  *
3215  * callbacks:
3216  *
3217  * exec()   calls init, apply, exit
3218  *
3219  * invoke() sets mouse coords in x,y
3220  * call init()
3221  * add modal handler
3222  *
3223  * modal()  accept modal events while doing it
3224  * call apply() with active window and nonactive window
3225  * call exit() and remove handler when LMB confirm
3226  */
3227 
3228 typedef struct sAreaJoinData {
3229   ScrArea *sa1;        /* first area to be considered */
3230   ScrArea *sa2;        /* second area to be considered */
3231   void *draw_callback; /* call `ED_screen_draw_join_shape` */
3232 
3233 } sAreaJoinData;
3234 
area_join_draw_cb(const struct wmWindow * UNUSED (win),void * userdata)3235 static void area_join_draw_cb(const struct wmWindow *UNUSED(win), void *userdata)
3236 {
3237   const wmOperator *op = userdata;
3238 
3239   sAreaJoinData *sd = op->customdata;
3240   if (sd->sa1 && sd->sa2) {
3241     ED_screen_draw_join_shape(sd->sa1, sd->sa2);
3242   }
3243 }
3244 
3245 /* validate selection inside screen, set variables OK */
3246 /* return false: init failed */
area_join_init(bContext * C,wmOperator * op,ScrArea * sa1,ScrArea * sa2)3247 static bool area_join_init(bContext *C, wmOperator *op, ScrArea *sa1, ScrArea *sa2)
3248 {
3249   if (sa1 == NULL || sa2 == NULL) {
3250     /* Get areas from cursor location if not specified. */
3251     int cursor[2];
3252     RNA_int_get_array(op->ptr, "cursor", cursor);
3253     screen_area_edge_from_cursor(C, cursor, &sa1, &sa2);
3254   }
3255   if (sa1 == NULL || sa2 == NULL) {
3256     return false;
3257   }
3258 
3259   sAreaJoinData *jd = MEM_callocN(sizeof(sAreaJoinData), "op_area_join");
3260 
3261   jd->sa1 = sa1;
3262   jd->sa2 = sa2;
3263 
3264   op->customdata = jd;
3265 
3266   jd->draw_callback = WM_draw_cb_activate(CTX_wm_window(C), area_join_draw_cb, op);
3267 
3268   return true;
3269 }
3270 
3271 /* apply the join of the areas (space types) */
area_join_apply(bContext * C,wmOperator * op)3272 static bool area_join_apply(bContext *C, wmOperator *op)
3273 {
3274   sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3275   if (!jd) {
3276     return false;
3277   }
3278 
3279   if (!screen_area_join(C, CTX_wm_screen(C), jd->sa1, jd->sa2)) {
3280     return false;
3281   }
3282   if (CTX_wm_area(C) == jd->sa2) {
3283     CTX_wm_area_set(C, NULL);
3284     CTX_wm_region_set(C, NULL);
3285   }
3286 
3287   return true;
3288 }
3289 
3290 /* finish operation */
area_join_exit(bContext * C,wmOperator * op)3291 static void area_join_exit(bContext *C, wmOperator *op)
3292 {
3293   sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3294 
3295   if (jd) {
3296     if (jd->draw_callback) {
3297       WM_draw_cb_exit(CTX_wm_window(C), jd->draw_callback);
3298     }
3299 
3300     MEM_freeN(jd);
3301     op->customdata = NULL;
3302   }
3303 
3304   /* this makes sure aligned edges will result in aligned grabbing */
3305   BKE_screen_remove_double_scredges(CTX_wm_screen(C));
3306   BKE_screen_remove_unused_scredges(CTX_wm_screen(C));
3307   BKE_screen_remove_unused_scrverts(CTX_wm_screen(C));
3308 }
3309 
area_join_exec(bContext * C,wmOperator * op)3310 static int area_join_exec(bContext *C, wmOperator *op)
3311 {
3312   if (!area_join_init(C, op, NULL, NULL)) {
3313     return OPERATOR_CANCELLED;
3314   }
3315 
3316   area_join_apply(C, op);
3317   area_join_exit(C, op);
3318 
3319   return OPERATOR_FINISHED;
3320 }
3321 
3322 /* interaction callback */
area_join_invoke(bContext * C,wmOperator * op,const wmEvent * event)3323 static int area_join_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3324 {
3325   if (event->type == EVT_ACTIONZONE_AREA) {
3326     sActionzoneData *sad = event->customdata;
3327 
3328     if (sad == NULL || sad->modifier > 0) {
3329       return OPERATOR_PASS_THROUGH;
3330     }
3331 
3332     /* verify *sad itself */
3333     if (sad->sa1 == NULL || sad->sa2 == NULL) {
3334       return OPERATOR_PASS_THROUGH;
3335     }
3336 
3337     /* is this our *sad? if areas equal it should be passed on */
3338     if (sad->sa1 == sad->sa2) {
3339       return OPERATOR_PASS_THROUGH;
3340     }
3341     if (!area_join_init(C, op, sad->sa1, sad->sa2)) {
3342       return OPERATOR_CANCELLED;
3343     }
3344   }
3345 
3346   /* add temp handler */
3347   WM_event_add_modal_handler(C, op);
3348 
3349   return OPERATOR_RUNNING_MODAL;
3350 }
3351 
area_join_cancel(bContext * C,wmOperator * op)3352 static void area_join_cancel(bContext *C, wmOperator *op)
3353 {
3354   WM_event_add_notifier(C, NC_WINDOW, NULL);
3355 
3356   area_join_exit(C, op);
3357 }
3358 
3359 /* modal callback while selecting area (space) that will be removed */
area_join_modal(bContext * C,wmOperator * op,const wmEvent * event)3360 static int area_join_modal(bContext *C, wmOperator *op, const wmEvent *event)
3361 {
3362   bScreen *screen = CTX_wm_screen(C);
3363   wmWindow *win = CTX_wm_window(C);
3364 
3365   if (op->customdata == NULL) {
3366     if (!area_join_init(C, op, NULL, NULL)) {
3367       return OPERATOR_CANCELLED;
3368     }
3369   }
3370   sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3371 
3372   /* execute the events */
3373   switch (event->type) {
3374 
3375     case MOUSEMOVE: {
3376       ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y);
3377       int dir = -1;
3378 
3379       if (area) {
3380         if (jd->sa1 != area) {
3381           dir = area_getorientation(jd->sa1, area);
3382           if (dir != -1) {
3383             jd->sa2 = area;
3384           }
3385           else {
3386             /* we are not bordering on the previously selected area
3387              * we check if area has common border with the one marked for removal
3388              * in this case we can swap areas.
3389              */
3390             dir = area_getorientation(area, jd->sa2);
3391             if (dir != -1) {
3392               jd->sa1 = jd->sa2;
3393               jd->sa2 = area;
3394             }
3395             else {
3396               jd->sa2 = NULL;
3397             }
3398           }
3399           WM_event_add_notifier(C, NC_WINDOW, NULL);
3400         }
3401         else {
3402           /* we are back in the area previously selected for keeping
3403            * we swap the areas if possible to allow user to choose */
3404           if (jd->sa2 != NULL) {
3405             jd->sa1 = jd->sa2;
3406             jd->sa2 = area;
3407             dir = area_getorientation(jd->sa1, jd->sa2);
3408             if (dir == -1) {
3409               printf("oops, didn't expect that!\n");
3410             }
3411           }
3412           else {
3413             dir = area_getorientation(jd->sa1, area);
3414             if (dir != -1) {
3415               jd->sa2 = area;
3416             }
3417           }
3418           WM_event_add_notifier(C, NC_WINDOW, NULL);
3419         }
3420       }
3421 
3422       if (dir == 1) {
3423         WM_cursor_set(win, WM_CURSOR_N_ARROW);
3424       }
3425       else if (dir == 3) {
3426         WM_cursor_set(win, WM_CURSOR_S_ARROW);
3427       }
3428       else if (dir == 2) {
3429         WM_cursor_set(win, WM_CURSOR_E_ARROW);
3430       }
3431       else if (dir == 0) {
3432         WM_cursor_set(win, WM_CURSOR_W_ARROW);
3433       }
3434       else {
3435         WM_cursor_set(win, WM_CURSOR_STOP);
3436       }
3437 
3438       break;
3439     }
3440     case LEFTMOUSE:
3441       if (event->val == KM_RELEASE) {
3442         ED_area_tag_redraw(jd->sa1);
3443         ED_area_tag_redraw(jd->sa2);
3444 
3445         area_join_apply(C, op);
3446         WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3447         area_join_exit(C, op);
3448         return OPERATOR_FINISHED;
3449       }
3450       break;
3451 
3452     case RIGHTMOUSE:
3453     case EVT_ESCKEY:
3454       area_join_cancel(C, op);
3455       return OPERATOR_CANCELLED;
3456   }
3457 
3458   return OPERATOR_RUNNING_MODAL;
3459 }
3460 
3461 /* Operator for joining two areas (space types) */
SCREEN_OT_area_join(wmOperatorType * ot)3462 static void SCREEN_OT_area_join(wmOperatorType *ot)
3463 {
3464   /* identifiers */
3465   ot->name = "Join Area";
3466   ot->description = "Join selected areas into new window";
3467   ot->idname = "SCREEN_OT_area_join";
3468 
3469   /* api callbacks */
3470   ot->exec = area_join_exec;
3471   ot->invoke = area_join_invoke;
3472   ot->modal = area_join_modal;
3473   ot->poll = screen_active_editable;
3474   ot->cancel = area_join_cancel;
3475 
3476   /* flags */
3477   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
3478 
3479   /* rna */
3480   RNA_def_int_vector(
3481       ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX);
3482 }
3483 
3484 /** \} */
3485 
3486 /* -------------------------------------------------------------------- */
3487 /** \name Screen Area Options Operator
3488  * \{ */
3489 
screen_area_options_invoke(bContext * C,wmOperator * op,const wmEvent * event)3490 static int screen_area_options_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3491 {
3492   ScrArea *sa1, *sa2;
3493   if (screen_area_edge_from_cursor(C, &event->x, &sa1, &sa2) == NULL) {
3494     return OPERATOR_CANCELLED;
3495   }
3496 
3497   uiPopupMenu *pup = UI_popup_menu_begin(C, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
3498   uiLayout *layout = UI_popup_menu_layout(pup);
3499 
3500   /* Vertical Split */
3501   PointerRNA ptr;
3502   uiItemFullO(layout,
3503               "SCREEN_OT_area_split",
3504               IFACE_("Vertical Split"),
3505               ICON_NONE,
3506               NULL,
3507               WM_OP_INVOKE_DEFAULT,
3508               0,
3509               &ptr);
3510   /* store initial mouse cursor position. */
3511   RNA_int_set_array(&ptr, "cursor", &event->x);
3512   RNA_enum_set(&ptr, "direction", 'v');
3513 
3514   /* Horizontal Split */
3515   uiItemFullO(layout,
3516               "SCREEN_OT_area_split",
3517               IFACE_("Horizontal Split"),
3518               ICON_NONE,
3519               NULL,
3520               WM_OP_INVOKE_DEFAULT,
3521               0,
3522               &ptr);
3523   /* store initial mouse cursor position. */
3524   RNA_int_set_array(&ptr, "cursor", &event->x);
3525   RNA_enum_set(&ptr, "direction", 'h');
3526 
3527   if (sa1 && sa2) {
3528     uiItemS(layout);
3529   }
3530 
3531   /* Join needs two very similar areas. */
3532   if (sa1 && sa2 && (area_getorientation(sa1, sa2) != -1)) {
3533     uiItemFullO(layout,
3534                 "SCREEN_OT_area_join",
3535                 IFACE_("Join Areas"),
3536                 ICON_NONE,
3537                 NULL,
3538                 WM_OP_INVOKE_DEFAULT,
3539                 0,
3540                 &ptr);
3541     RNA_int_set_array(&ptr, "cursor", &event->x);
3542   }
3543 
3544   /* Swap just needs two areas. */
3545   if (sa1 && sa2) {
3546     uiItemFullO(layout,
3547                 "SCREEN_OT_area_swap",
3548                 IFACE_("Swap Areas"),
3549                 ICON_NONE,
3550                 NULL,
3551                 WM_OP_EXEC_DEFAULT,
3552                 0,
3553                 &ptr);
3554     RNA_int_set_array(&ptr, "cursor", &event->x);
3555   }
3556 
3557   UI_popup_menu_end(C, pup);
3558 
3559   return OPERATOR_INTERFACE;
3560 }
3561 
SCREEN_OT_area_options(wmOperatorType * ot)3562 static void SCREEN_OT_area_options(wmOperatorType *ot)
3563 {
3564   /* identifiers */
3565   ot->name = "Area Options";
3566   ot->description = "Operations for splitting and merging";
3567   ot->idname = "SCREEN_OT_area_options";
3568 
3569   /* api callbacks */
3570   ot->invoke = screen_area_options_invoke;
3571 
3572   ot->poll = ED_operator_screen_mainwinactive;
3573 
3574   /* flags */
3575   ot->flag = OPTYPE_INTERNAL;
3576 }
3577 
3578 /** \} */
3579 
3580 /* -------------------------------------------------------------------- */
3581 /** \name Space Data Cleanup Operator
3582  * \{ */
3583 
spacedata_cleanup_exec(bContext * C,wmOperator * op)3584 static int spacedata_cleanup_exec(bContext *C, wmOperator *op)
3585 {
3586   Main *bmain = CTX_data_main(C);
3587   int tot = 0;
3588 
3589   LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
3590     LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
3591       if (area->spacedata.first != area->spacedata.last) {
3592         SpaceLink *sl = area->spacedata.first;
3593 
3594         BLI_remlink(&area->spacedata, sl);
3595         tot += BLI_listbase_count(&area->spacedata);
3596         BKE_spacedata_freelist(&area->spacedata);
3597         BLI_addtail(&area->spacedata, sl);
3598       }
3599     }
3600   }
3601   BKE_reportf(op->reports, RPT_INFO, "Removed amount of editors: %d", tot);
3602 
3603   return OPERATOR_FINISHED;
3604 }
3605 
SCREEN_OT_spacedata_cleanup(wmOperatorType * ot)3606 static void SCREEN_OT_spacedata_cleanup(wmOperatorType *ot)
3607 {
3608   /* identifiers */
3609   ot->name = "Clean-up Space-data";
3610   ot->description = "Remove unused settings for invisible editors";
3611   ot->idname = "SCREEN_OT_spacedata_cleanup";
3612 
3613   /* api callbacks */
3614   ot->exec = spacedata_cleanup_exec;
3615   ot->poll = WM_operator_winactive;
3616 }
3617 
3618 /** \} */
3619 
3620 /* -------------------------------------------------------------------- */
3621 /** \name Repeat Last Operator
3622  * \{ */
3623 
repeat_history_poll(bContext * C)3624 static bool repeat_history_poll(bContext *C)
3625 {
3626   if (!ED_operator_screenactive(C)) {
3627     return false;
3628   }
3629   wmWindowManager *wm = CTX_wm_manager(C);
3630   return !BLI_listbase_is_empty(&wm->operators);
3631 }
3632 
repeat_last_exec(bContext * C,wmOperator * UNUSED (op))3633 static int repeat_last_exec(bContext *C, wmOperator *UNUSED(op))
3634 {
3635   wmWindowManager *wm = CTX_wm_manager(C);
3636   wmOperator *lastop = wm->operators.last;
3637 
3638   /* Seek last registered operator */
3639   while (lastop) {
3640     if (lastop->type->flag & OPTYPE_REGISTER) {
3641       break;
3642     }
3643     lastop = lastop->prev;
3644   }
3645 
3646   if (lastop) {
3647     WM_operator_free_all_after(wm, lastop);
3648     WM_operator_repeat_last(C, lastop);
3649   }
3650 
3651   return OPERATOR_CANCELLED;
3652 }
3653 
SCREEN_OT_repeat_last(wmOperatorType * ot)3654 static void SCREEN_OT_repeat_last(wmOperatorType *ot)
3655 {
3656   /* identifiers */
3657   ot->name = "Repeat Last";
3658   ot->description = "Repeat last action";
3659   ot->idname = "SCREEN_OT_repeat_last";
3660 
3661   /* api callbacks */
3662   ot->exec = repeat_last_exec;
3663 
3664   ot->poll = repeat_history_poll;
3665 }
3666 
3667 /** \} */
3668 
3669 /* -------------------------------------------------------------------- */
3670 /** \name Repeat History Operator
3671  * \{ */
3672 
repeat_history_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))3673 static int repeat_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
3674 {
3675   wmWindowManager *wm = CTX_wm_manager(C);
3676 
3677   int items = BLI_listbase_count(&wm->operators);
3678   if (items == 0) {
3679     return OPERATOR_CANCELLED;
3680   }
3681 
3682   uiPopupMenu *pup = UI_popup_menu_begin(C, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
3683   uiLayout *layout = UI_popup_menu_layout(pup);
3684 
3685   wmOperator *lastop;
3686   int i;
3687   for (i = items - 1, lastop = wm->operators.last; lastop; lastop = lastop->prev, i--) {
3688     if ((lastop->type->flag & OPTYPE_REGISTER) && WM_operator_repeat_check(C, lastop)) {
3689       uiItemIntO(layout,
3690                  WM_operatortype_name(lastop->type, lastop->ptr),
3691                  ICON_NONE,
3692                  op->type->idname,
3693                  "index",
3694                  i);
3695     }
3696   }
3697 
3698   UI_popup_menu_end(C, pup);
3699 
3700   return OPERATOR_INTERFACE;
3701 }
3702 
repeat_history_exec(bContext * C,wmOperator * op)3703 static int repeat_history_exec(bContext *C, wmOperator *op)
3704 {
3705   wmWindowManager *wm = CTX_wm_manager(C);
3706 
3707   op = BLI_findlink(&wm->operators, RNA_int_get(op->ptr, "index"));
3708   if (op) {
3709     /* let's put it as last operator in list */
3710     BLI_remlink(&wm->operators, op);
3711     BLI_addtail(&wm->operators, op);
3712 
3713     WM_operator_repeat(C, op);
3714   }
3715 
3716   return OPERATOR_FINISHED;
3717 }
3718 
SCREEN_OT_repeat_history(wmOperatorType * ot)3719 static void SCREEN_OT_repeat_history(wmOperatorType *ot)
3720 {
3721   /* identifiers */
3722   ot->name = "Repeat History";
3723   ot->description = "Display menu for previous actions performed";
3724   ot->idname = "SCREEN_OT_repeat_history";
3725 
3726   /* api callbacks */
3727   ot->invoke = repeat_history_invoke;
3728   ot->exec = repeat_history_exec;
3729   ot->poll = repeat_history_poll;
3730 
3731   RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
3732 }
3733 
3734 /** \} */
3735 
3736 /* -------------------------------------------------------------------- */
3737 /** \name Redo Operator
3738  * \{ */
3739 
redo_last_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * UNUSED (event))3740 static int redo_last_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event))
3741 {
3742   wmOperator *lastop = WM_operator_last_redo(C);
3743 
3744   if (lastop) {
3745     WM_operator_redo_popup(C, lastop);
3746   }
3747 
3748   return OPERATOR_CANCELLED;
3749 }
3750 
SCREEN_OT_redo_last(wmOperatorType * ot)3751 static void SCREEN_OT_redo_last(wmOperatorType *ot)
3752 {
3753   /* identifiers */
3754   ot->name = "Redo Last";
3755   ot->description = "Display parameters for last action performed";
3756   ot->idname = "SCREEN_OT_redo_last";
3757 
3758   /* api callbacks */
3759   ot->invoke = redo_last_invoke;
3760   ot->poll = repeat_history_poll;
3761 }
3762 
3763 /** \} */
3764 
3765 /* -------------------------------------------------------------------- */
3766 /** \name Region Quad-View Operator
3767  * \{ */
3768 
view3d_localview_update_rv3d(struct RegionView3D * rv3d)3769 static void view3d_localview_update_rv3d(struct RegionView3D *rv3d)
3770 {
3771   if (rv3d->localvd) {
3772     rv3d->localvd->view = rv3d->view;
3773     rv3d->localvd->persp = rv3d->persp;
3774     copy_qt_qt(rv3d->localvd->viewquat, rv3d->viewquat);
3775   }
3776 }
3777 
region_quadview_init_rv3d(ScrArea * area,ARegion * region,const char viewlock,const char view,const char persp)3778 static void region_quadview_init_rv3d(
3779     ScrArea *area, ARegion *region, const char viewlock, const char view, const char persp)
3780 {
3781   RegionView3D *rv3d = region->regiondata;
3782 
3783   if (persp == RV3D_CAMOB) {
3784     ED_view3d_lastview_store(rv3d);
3785   }
3786 
3787   rv3d->viewlock = viewlock;
3788   rv3d->runtime_viewlock = 0;
3789   rv3d->view = view;
3790   rv3d->view_axis_roll = RV3D_VIEW_AXIS_ROLL_0;
3791   rv3d->persp = persp;
3792 
3793   ED_view3d_lock(rv3d);
3794   view3d_localview_update_rv3d(rv3d);
3795   if ((viewlock & RV3D_BOXCLIP) && (persp == RV3D_ORTHO)) {
3796     ED_view3d_quadview_update(area, region, true);
3797   }
3798 }
3799 
3800 /* insert a region in the area region list */
region_quadview_exec(bContext * C,wmOperator * op)3801 static int region_quadview_exec(bContext *C, wmOperator *op)
3802 {
3803   ARegion *region = CTX_wm_region(C);
3804 
3805   /* some rules... */
3806   if (region->regiontype != RGN_TYPE_WINDOW) {
3807     BKE_report(op->reports, RPT_ERROR, "Only window region can be 4-split");
3808   }
3809   else if (region->alignment == RGN_ALIGN_QSPLIT) {
3810     /* Exit quad-view */
3811     ScrArea *area = CTX_wm_area(C);
3812 
3813     /* keep current region */
3814     region->alignment = 0;
3815 
3816     if (area->spacetype == SPACE_VIEW3D) {
3817       RegionView3D *rv3d = region->regiondata;
3818 
3819       /* if this is a locked view, use settings from 'User' view */
3820       if (rv3d->viewlock) {
3821         View3D *v3d_user;
3822         ARegion *region_user;
3823 
3824         if (ED_view3d_context_user_region(C, &v3d_user, &region_user)) {
3825           if (region != region_user) {
3826             SWAP(void *, region->regiondata, region_user->regiondata);
3827             rv3d = region->regiondata;
3828           }
3829         }
3830       }
3831 
3832       rv3d->viewlock_quad = RV3D_VIEWLOCK_INIT;
3833       rv3d->viewlock = 0;
3834 
3835       /* FIXME: This fixes missing update to workbench TAA. (see T76216)
3836        * However, it would be nice if the tagging should be done in a more conventional way. */
3837       rv3d->rflag |= RV3D_GPULIGHT_UPDATE;
3838 
3839       /* Accumulate locks, in case they're mixed. */
3840       LISTBASE_FOREACH (ARegion *, region_iter, &area->regionbase) {
3841         if (region_iter->regiontype == RGN_TYPE_WINDOW) {
3842           RegionView3D *rv3d_iter = region_iter->regiondata;
3843           rv3d->viewlock_quad |= rv3d_iter->viewlock;
3844         }
3845       }
3846     }
3847 
3848     LISTBASE_FOREACH_MUTABLE (ARegion *, region_iter, &area->regionbase) {
3849       if (region_iter->alignment == RGN_ALIGN_QSPLIT) {
3850         ED_region_remove(C, area, region_iter);
3851       }
3852     }
3853     ED_area_tag_redraw(area);
3854     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3855   }
3856   else if (region->next) {
3857     BKE_report(op->reports, RPT_ERROR, "Only last region can be 4-split");
3858   }
3859   else {
3860     /* Enter quad-view */
3861     ScrArea *area = CTX_wm_area(C);
3862 
3863     region->alignment = RGN_ALIGN_QSPLIT;
3864 
3865     for (int count = 0; count < 3; count++) {
3866       ARegion *new_region = BKE_area_region_copy(area->type, region);
3867       BLI_addtail(&area->regionbase, new_region);
3868     }
3869 
3870     /* lock views and set them */
3871     if (area->spacetype == SPACE_VIEW3D) {
3872       View3D *v3d = area->spacedata.first;
3873       int index_qsplit = 0;
3874 
3875       /* run ED_view3d_lock() so the correct 'rv3d->viewquat' is set,
3876        * otherwise when restoring rv3d->localvd the 'viewquat' won't
3877        * match the 'view', set on entering localview See: T26315,
3878        *
3879        * We could avoid manipulating rv3d->localvd here if exiting
3880        * localview with a 4-split would assign these view locks */
3881       RegionView3D *rv3d = region->regiondata;
3882       const char viewlock = (rv3d->viewlock_quad & RV3D_VIEWLOCK_INIT) ?
3883                                 (rv3d->viewlock_quad & ~RV3D_VIEWLOCK_INIT) :
3884                                 RV3D_LOCK_ROTATION;
3885 
3886       region_quadview_init_rv3d(
3887           area, region, viewlock, ED_view3d_lock_view_from_index(index_qsplit++), RV3D_ORTHO);
3888       region_quadview_init_rv3d(area,
3889                                 (region = region->next),
3890                                 viewlock,
3891                                 ED_view3d_lock_view_from_index(index_qsplit++),
3892                                 RV3D_ORTHO);
3893       region_quadview_init_rv3d(area,
3894                                 (region = region->next),
3895                                 viewlock,
3896                                 ED_view3d_lock_view_from_index(index_qsplit++),
3897                                 RV3D_ORTHO);
3898       /* forcing camera is distracting */
3899 #if 0
3900       if (v3d->camera) {
3901         region_quadview_init_rv3d(area, (region = region->next), 0, RV3D_VIEW_CAMERA, RV3D_CAMOB);
3902       }
3903       else {
3904         region_quadview_init_rv3d(area, (region = region->next), 0, RV3D_VIEW_USER, RV3D_PERSP);
3905       }
3906 #else
3907       (void)v3d;
3908 #endif
3909     }
3910     ED_area_tag_redraw(area);
3911     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3912   }
3913 
3914   return OPERATOR_FINISHED;
3915 }
3916 
SCREEN_OT_region_quadview(wmOperatorType * ot)3917 static void SCREEN_OT_region_quadview(wmOperatorType *ot)
3918 {
3919   /* identifiers */
3920   ot->name = "Toggle Quad View";
3921   ot->description = "Split selected area into camera, front, right & top views";
3922   ot->idname = "SCREEN_OT_region_quadview";
3923 
3924   /* api callbacks */
3925   ot->exec = region_quadview_exec;
3926   ot->poll = ED_operator_region_view3d_active;
3927   ot->flag = 0;
3928 }
3929 
3930 /** \} */
3931 
3932 /* -------------------------------------------------------------------- */
3933 /** \name Region Toggle Operator
3934  * \{ */
3935 
region_toggle_exec(bContext * C,wmOperator * op)3936 static int region_toggle_exec(bContext *C, wmOperator *op)
3937 {
3938   PropertyRNA *prop = RNA_struct_find_property(op->ptr, "region_type");
3939 
3940   ARegion *region;
3941   if (RNA_property_is_set(op->ptr, prop)) {
3942     region = BKE_area_find_region_type(CTX_wm_area(C), RNA_property_enum_get(op->ptr, prop));
3943   }
3944   else {
3945     region = CTX_wm_region(C);
3946   }
3947 
3948   if (region && (region->alignment != RGN_ALIGN_NONE)) {
3949     ED_region_toggle_hidden(C, region);
3950   }
3951   ED_region_tag_redraw(region);
3952 
3953   return OPERATOR_FINISHED;
3954 }
3955 
region_toggle_poll(bContext * C)3956 static bool region_toggle_poll(bContext *C)
3957 {
3958   ScrArea *area = CTX_wm_area(C);
3959 
3960   /* Don't flip anything around in top-bar. */
3961   if (area && area->spacetype == SPACE_TOPBAR) {
3962     CTX_wm_operator_poll_msg_set(C, "Toggling regions in the Top-bar is not allowed");
3963     return false;
3964   }
3965 
3966   return ED_operator_areaactive(C);
3967 }
3968 
SCREEN_OT_region_toggle(wmOperatorType * ot)3969 static void SCREEN_OT_region_toggle(wmOperatorType *ot)
3970 {
3971   /* identifiers */
3972   ot->name = "Toggle Region";
3973   ot->idname = "SCREEN_OT_region_toggle";
3974   ot->description = "Hide or unhide the region";
3975 
3976   /* api callbacks */
3977   ot->exec = region_toggle_exec;
3978   ot->poll = region_toggle_poll;
3979   ot->flag = 0;
3980 
3981   RNA_def_enum(ot->srna,
3982                "region_type",
3983                rna_enum_region_type_items,
3984                0,
3985                "Region Type",
3986                "Type of the region to toggle");
3987 }
3988 
3989 /** \} */
3990 
3991 /* -------------------------------------------------------------------- */
3992 /** \name Region Flip Operator
3993  * \{ */
3994 
3995 /* flip a region alignment */
region_flip_exec(bContext * C,wmOperator * UNUSED (op))3996 static int region_flip_exec(bContext *C, wmOperator *UNUSED(op))
3997 {
3998   ARegion *region = CTX_wm_region(C);
3999 
4000   if (!region) {
4001     return OPERATOR_CANCELLED;
4002   }
4003 
4004   if (region->alignment == RGN_ALIGN_TOP) {
4005     region->alignment = RGN_ALIGN_BOTTOM;
4006   }
4007   else if (region->alignment == RGN_ALIGN_BOTTOM) {
4008     region->alignment = RGN_ALIGN_TOP;
4009   }
4010   else if (region->alignment == RGN_ALIGN_LEFT) {
4011     region->alignment = RGN_ALIGN_RIGHT;
4012   }
4013   else if (region->alignment == RGN_ALIGN_RIGHT) {
4014     region->alignment = RGN_ALIGN_LEFT;
4015   }
4016 
4017   ED_area_tag_redraw(CTX_wm_area(C));
4018   WM_event_add_mousemove(CTX_wm_window(C));
4019   WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
4020 
4021   return OPERATOR_FINISHED;
4022 }
4023 
region_flip_poll(bContext * C)4024 static bool region_flip_poll(bContext *C)
4025 {
4026   ScrArea *area = CTX_wm_area(C);
4027 
4028   /* Don't flip anything around in top-bar. */
4029   if (area && area->spacetype == SPACE_TOPBAR) {
4030     CTX_wm_operator_poll_msg_set(C, "Flipping regions in the Top-bar is not allowed");
4031     return 0;
4032   }
4033 
4034   return ED_operator_areaactive(C);
4035 }
4036 
SCREEN_OT_region_flip(wmOperatorType * ot)4037 static void SCREEN_OT_region_flip(wmOperatorType *ot)
4038 {
4039   /* identifiers */
4040   ot->name = "Flip Region";
4041   ot->idname = "SCREEN_OT_region_flip";
4042   ot->description = "Toggle the region's alignment (left/right or top/bottom)";
4043 
4044   /* api callbacks */
4045   ot->exec = region_flip_exec;
4046   ot->poll = region_flip_poll;
4047   ot->flag = 0;
4048 }
4049 
4050 /** \} */
4051 
4052 /* -------------------------------------------------------------------- */
4053 /** \name Header Toggle Menu Operator
4054  * \{ */
4055 
4056 /* show/hide header text menus */
header_toggle_menus_exec(bContext * C,wmOperator * UNUSED (op))4057 static int header_toggle_menus_exec(bContext *C, wmOperator *UNUSED(op))
4058 {
4059   ScrArea *area = CTX_wm_area(C);
4060 
4061   area->flag = area->flag ^ HEADER_NO_PULLDOWN;
4062 
4063   ED_area_tag_redraw(area);
4064   WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
4065 
4066   return OPERATOR_FINISHED;
4067 }
4068 
SCREEN_OT_header_toggle_menus(wmOperatorType * ot)4069 static void SCREEN_OT_header_toggle_menus(wmOperatorType *ot)
4070 {
4071   /* identifiers */
4072   ot->name = "Expand/Collapse Header Menus";
4073   ot->idname = "SCREEN_OT_header_toggle_menus";
4074   ot->description = "Expand or collapse the header pulldown menus";
4075 
4076   /* api callbacks */
4077   ot->exec = header_toggle_menus_exec;
4078   ot->poll = ED_operator_areaactive;
4079   ot->flag = 0;
4080 }
4081 
4082 /** \} */
4083 
4084 /* -------------------------------------------------------------------- */
4085 /** \name Region Context Menu Operator (Header/Footer/Navbar)
4086  * \{ */
4087 
ED_screens_header_tools_menu_create(bContext * C,uiLayout * layout,void * UNUSED (arg))4088 void ED_screens_header_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg))
4089 {
4090   ScrArea *area = CTX_wm_area(C);
4091   ARegion *region = CTX_wm_region(C);
4092   const char *but_flip_str = (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_TOP) ?
4093                                  IFACE_("Flip to Bottom") :
4094                                  IFACE_("Flip to Top");
4095   {
4096     PointerRNA ptr;
4097     RNA_pointer_create((ID *)CTX_wm_screen(C), &RNA_Space, area->spacedata.first, &ptr);
4098     if (!ELEM(area->spacetype, SPACE_TOPBAR)) {
4099       uiItemR(layout, &ptr, "show_region_header", 0, IFACE_("Show Header"), ICON_NONE);
4100     }
4101 
4102     ARegion *region_header = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
4103     uiLayout *col = uiLayoutColumn(layout, 0);
4104     uiLayoutSetActive(col, (region_header->flag & RGN_FLAG_HIDDEN) == 0);
4105 
4106     if (BKE_area_find_region_type(area, RGN_TYPE_TOOL_HEADER)) {
4107       uiItemR(col, &ptr, "show_region_tool_header", 0, IFACE_("Show Tool Settings"), ICON_NONE);
4108     }
4109 
4110     uiItemO(col,
4111             IFACE_("Show Menus"),
4112             (area->flag & HEADER_NO_PULLDOWN) ? ICON_CHECKBOX_DEHLT : ICON_CHECKBOX_HLT,
4113             "SCREEN_OT_header_toggle_menus");
4114   }
4115 
4116   /* default is WM_OP_INVOKE_REGION_WIN, which we don't want here. */
4117   uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
4118 
4119   if (!ELEM(area->spacetype, SPACE_TOPBAR)) {
4120     uiItemS(layout);
4121 
4122     uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip");
4123   }
4124 
4125   /* File browser should be fullscreen all the time, top-bar should
4126    * never be. But other regions can be maximized/restored. */
4127   if (!ELEM(area->spacetype, SPACE_FILE, SPACE_TOPBAR)) {
4128     uiItemS(layout);
4129 
4130     const char *but_str = area->full ? IFACE_("Tile Area") : IFACE_("Maximize Area");
4131     uiItemO(layout, but_str, ICON_NONE, "SCREEN_OT_screen_full_area");
4132   }
4133 }
4134 
ED_screens_footer_tools_menu_create(bContext * C,uiLayout * layout,void * UNUSED (arg))4135 void ED_screens_footer_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg))
4136 {
4137   ScrArea *area = CTX_wm_area(C);
4138   ARegion *region = CTX_wm_region(C);
4139   const char *but_flip_str = (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_TOP) ?
4140                                  IFACE_("Flip to Bottom") :
4141                                  IFACE_("Flip to Top");
4142   {
4143     PointerRNA ptr;
4144     RNA_pointer_create((ID *)CTX_wm_screen(C), &RNA_Space, area->spacedata.first, &ptr);
4145     uiItemR(layout, &ptr, "show_region_footer", 0, IFACE_("Show Footer"), ICON_NONE);
4146   }
4147 
4148   /* default is WM_OP_INVOKE_REGION_WIN, which we don't want here. */
4149   uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
4150 
4151   uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip");
4152 
4153   /* File browser should be fullscreen all the time, top-bar should
4154    * never be. But other regions can be maximized/restored... */
4155   if (!ELEM(area->spacetype, SPACE_FILE, SPACE_TOPBAR)) {
4156     uiItemS(layout);
4157 
4158     const char *but_str = area->full ? IFACE_("Tile Area") : IFACE_("Maximize Area");
4159     uiItemO(layout, but_str, ICON_NONE, "SCREEN_OT_screen_full_area");
4160   }
4161 }
4162 
ED_screens_navigation_bar_tools_menu_create(bContext * C,uiLayout * layout,void * UNUSED (arg))4163 void ED_screens_navigation_bar_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg))
4164 {
4165   const ARegion *region = CTX_wm_region(C);
4166   const char *but_flip_str = (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_LEFT) ?
4167                                  IFACE_("Flip to Right") :
4168                                  IFACE_("Flip to Left");
4169 
4170   /* default is WM_OP_INVOKE_REGION_WIN, which we don't want here. */
4171   uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
4172 
4173   uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip");
4174 }
4175 
ed_screens_statusbar_menu_create(uiLayout * layout,void * UNUSED (arg))4176 static void ed_screens_statusbar_menu_create(uiLayout *layout, void *UNUSED(arg))
4177 {
4178   PointerRNA ptr;
4179 
4180   RNA_pointer_create(NULL, &RNA_PreferencesView, &U, &ptr);
4181   uiItemR(layout, &ptr, "show_statusbar_stats", 0, IFACE_("Scene Statistics"), ICON_NONE);
4182   uiItemR(layout, &ptr, "show_statusbar_memory", 0, IFACE_("System Memory"), ICON_NONE);
4183   if (GPU_mem_stats_supported()) {
4184     uiItemR(layout, &ptr, "show_statusbar_vram", 0, IFACE_("Video Memory"), ICON_NONE);
4185   }
4186   uiItemR(layout, &ptr, "show_statusbar_version", 0, IFACE_("Blender Version"), ICON_NONE);
4187 }
4188 
screen_context_menu_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * UNUSED (event))4189 static int screen_context_menu_invoke(bContext *C,
4190                                       wmOperator *UNUSED(op),
4191                                       const wmEvent *UNUSED(event))
4192 {
4193   const ScrArea *area = CTX_wm_area(C);
4194   const ARegion *region = CTX_wm_region(C);
4195 
4196   if (area && area->spacetype == SPACE_STATUSBAR) {
4197     uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Status Bar"), ICON_NONE);
4198     uiLayout *layout = UI_popup_menu_layout(pup);
4199     ed_screens_statusbar_menu_create(layout, NULL);
4200     UI_popup_menu_end(C, pup);
4201   }
4202   else if (ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
4203     uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Header"), ICON_NONE);
4204     uiLayout *layout = UI_popup_menu_layout(pup);
4205     ED_screens_header_tools_menu_create(C, layout, NULL);
4206     UI_popup_menu_end(C, pup);
4207   }
4208   else if (region->regiontype == RGN_TYPE_FOOTER) {
4209     uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Footer"), ICON_NONE);
4210     uiLayout *layout = UI_popup_menu_layout(pup);
4211     ED_screens_footer_tools_menu_create(C, layout, NULL);
4212     UI_popup_menu_end(C, pup);
4213   }
4214   else if (region->regiontype == RGN_TYPE_NAV_BAR) {
4215     uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Navigation Bar"), ICON_NONE);
4216     uiLayout *layout = UI_popup_menu_layout(pup);
4217     ED_screens_navigation_bar_tools_menu_create(C, layout, NULL);
4218     UI_popup_menu_end(C, pup);
4219   }
4220 
4221   return OPERATOR_INTERFACE;
4222 }
4223 
SCREEN_OT_region_context_menu(wmOperatorType * ot)4224 static void SCREEN_OT_region_context_menu(wmOperatorType *ot)
4225 {
4226   /* identifiers */
4227   ot->name = "Region Context Menu";
4228   ot->description = "Display region context menu";
4229   ot->idname = "SCREEN_OT_region_context_menu";
4230 
4231   /* api callbacks */
4232   ot->invoke = screen_context_menu_invoke;
4233 }
4234 
4235 /** \} */
4236 
4237 /* -------------------------------------------------------------------- */
4238 /** \name Animation Step Operator
4239  *
4240  * Animation Step.
4241  * \{ */
screen_animation_region_supports_time_follow(eSpace_Type spacetype,eRegionType regiontype)4242 static bool screen_animation_region_supports_time_follow(eSpace_Type spacetype,
4243                                                          eRegionType regiontype)
4244 {
4245   return (regiontype == RGN_TYPE_WINDOW &&
4246           ELEM(spacetype, SPACE_SEQ, SPACE_GRAPH, SPACE_ACTION, SPACE_NLA)) ||
4247          (spacetype == SPACE_CLIP && regiontype == RGN_TYPE_PREVIEW);
4248 }
4249 
match_region_with_redraws(const ScrArea * area,eRegionType regiontype,eScreen_Redraws_Flag redraws,bool from_anim_edit)4250 static bool match_region_with_redraws(const ScrArea *area,
4251                                       eRegionType regiontype,
4252                                       eScreen_Redraws_Flag redraws,
4253                                       bool from_anim_edit)
4254 {
4255   const eSpace_Type spacetype = area->spacetype;
4256   if (regiontype == RGN_TYPE_WINDOW) {
4257 
4258     switch (spacetype) {
4259       case SPACE_VIEW3D:
4260         if ((redraws & TIME_ALL_3D_WIN) || from_anim_edit) {
4261           return true;
4262         }
4263         break;
4264       case SPACE_GRAPH:
4265       case SPACE_NLA:
4266         if ((redraws & TIME_ALL_ANIM_WIN) || from_anim_edit) {
4267           return true;
4268         }
4269         break;
4270       case SPACE_ACTION:
4271         /* if only 1 window or 3d windows, we do timeline too
4272          * NOTE: Now we do action editor in all these cases, since timeline is here. */
4273         if ((redraws & (TIME_ALL_ANIM_WIN | TIME_REGION | TIME_ALL_3D_WIN)) || from_anim_edit) {
4274           return true;
4275         }
4276         break;
4277       case SPACE_PROPERTIES:
4278         if (redraws & TIME_ALL_BUTS_WIN) {
4279           return true;
4280         }
4281         break;
4282       case SPACE_SEQ:
4283         if ((redraws & (TIME_SEQ | TIME_ALL_ANIM_WIN)) || from_anim_edit) {
4284           return true;
4285         }
4286         break;
4287       case SPACE_NODE:
4288         if (redraws & TIME_NODES) {
4289           return true;
4290         }
4291         break;
4292       case SPACE_IMAGE:
4293         if ((redraws & TIME_ALL_IMAGE_WIN) || from_anim_edit) {
4294           return true;
4295         }
4296         break;
4297       case SPACE_CLIP:
4298         if ((redraws & TIME_CLIPS) || from_anim_edit) {
4299           return true;
4300         }
4301         break;
4302       default:
4303         break;
4304     }
4305   }
4306   else if (regiontype == RGN_TYPE_UI) {
4307     if (spacetype == SPACE_CLIP) {
4308       /* Track Preview button is on Properties Editor in SpaceClip,
4309        * and it's very common case when users want it be refreshing
4310        * during playback, so asking people to enable special option
4311        * for this is a bit tricky, so add exception here for refreshing
4312        * Properties Editor for SpaceClip always */
4313       return true;
4314     }
4315 
4316     if (redraws & TIME_ALL_BUTS_WIN) {
4317       return true;
4318     }
4319   }
4320   else if (regiontype == RGN_TYPE_HEADER) {
4321     if (spacetype == SPACE_ACTION) {
4322       /* The timeline shows the current frame in the header. Other headers
4323        * don't need to be updated. */
4324       SpaceAction *saction = (SpaceAction *)area->spacedata.first;
4325       return saction->mode == SACTCONT_TIMELINE;
4326     }
4327   }
4328   else if (regiontype == RGN_TYPE_PREVIEW) {
4329     switch (spacetype) {
4330       case SPACE_SEQ:
4331         if (redraws & (TIME_SEQ | TIME_ALL_ANIM_WIN)) {
4332           return true;
4333         }
4334         break;
4335       case SPACE_CLIP:
4336         return true;
4337       default:
4338         break;
4339     }
4340   }
4341   return false;
4342 }
4343 
screen_animation_region_tag_redraw(ScrArea * area,ARegion * region,const Scene * scene,eScreen_Redraws_Flag redraws)4344 static void screen_animation_region_tag_redraw(ScrArea *area,
4345                                                ARegion *region,
4346                                                const Scene *scene,
4347                                                eScreen_Redraws_Flag redraws)
4348 {
4349   /* Do follow time here if editor type supports it */
4350   if ((redraws & TIME_FOLLOW) &&
4351       (screen_animation_region_supports_time_follow(area->spacetype, region->regiontype))) {
4352     float w = BLI_rctf_size_x(&region->v2d.cur);
4353     if (scene->r.cfra < region->v2d.cur.xmin) {
4354       region->v2d.cur.xmax = scene->r.cfra;
4355       region->v2d.cur.xmin = region->v2d.cur.xmax - w;
4356       ED_region_tag_redraw(region);
4357       return;
4358     }
4359     if (scene->r.cfra > region->v2d.cur.xmax) {
4360       region->v2d.cur.xmin = scene->r.cfra;
4361       region->v2d.cur.xmax = region->v2d.cur.xmin + w;
4362       ED_region_tag_redraw(region);
4363       return;
4364     }
4365   }
4366 
4367   /* No need to do a full redraw as the current frame indicator is only updated.
4368    * We do need to redraw when this area is in full screen as no other areas
4369    * will be tagged for redrawing. */
4370   if ((region->regiontype == RGN_TYPE_WINDOW) &&
4371       (ELEM(area->spacetype, SPACE_GRAPH, SPACE_NLA, SPACE_ACTION)) && !area->full) {
4372     return;
4373   }
4374   ED_region_tag_redraw(region);
4375 }
4376 
4377 //#define PROFILE_AUDIO_SYNCH
4378 
screen_animation_step_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * event)4379 static int screen_animation_step_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
4380 {
4381   bScreen *screen = CTX_wm_screen(C);
4382   wmTimer *wt = screen->animtimer;
4383 
4384   if (!(wt && wt == event->customdata)) {
4385     return OPERATOR_PASS_THROUGH;
4386   }
4387 
4388   wmWindow *win = CTX_wm_window(C);
4389 
4390 #ifdef PROFILE_AUDIO_SYNCH
4391   static int old_frame = 0;
4392   int newfra_int;
4393 #endif
4394 
4395   Main *bmain = CTX_data_main(C);
4396   Scene *scene = CTX_data_scene(C);
4397   ViewLayer *view_layer = WM_window_get_active_view_layer(win);
4398   Depsgraph *depsgraph = BKE_scene_get_depsgraph(scene, view_layer);
4399   Scene *scene_eval = (depsgraph != NULL) ? DEG_get_evaluated_scene(depsgraph) : NULL;
4400   ScreenAnimData *sad = wt->customdata;
4401   wmWindowManager *wm = CTX_wm_manager(C);
4402   int sync;
4403   double time;
4404 
4405   /* sync, don't sync, or follow scene setting */
4406   if (sad->flag & ANIMPLAY_FLAG_SYNC) {
4407     sync = 1;
4408   }
4409   else if (sad->flag & ANIMPLAY_FLAG_NO_SYNC) {
4410     sync = 0;
4411   }
4412   else {
4413     sync = (scene->flag & SCE_FRAME_DROP);
4414   }
4415 
4416   if (scene_eval == NULL) {
4417     /* Happens when undo/redo system is used during playback, nothing meaningful we can do here. */
4418   }
4419   else if (scene_eval->id.recalc & ID_RECALC_AUDIO_SEEK) {
4420     /* Ignore seek here, the audio will be updated to the scene frame after jump during next
4421      * dependency graph update. */
4422   }
4423   else if ((scene->audio.flag & AUDIO_SYNC) && (sad->flag & ANIMPLAY_FLAG_REVERSE) == false &&
4424            isfinite(time = BKE_sound_sync_scene(scene_eval))) {
4425     double newfra = time * FPS;
4426 
4427     /* give some space here to avoid jumps */
4428     if (newfra + 0.5 > scene->r.cfra && newfra - 0.5 < scene->r.cfra) {
4429       scene->r.cfra++;
4430     }
4431     else {
4432       scene->r.cfra = max_ii(scene->r.cfra, round(newfra));
4433     }
4434 
4435 #ifdef PROFILE_AUDIO_SYNCH
4436     newfra_int = scene->r.cfra;
4437     if (newfra_int < old_frame) {
4438       printf("back jump detected, frame %d!\n", newfra_int);
4439     }
4440     else if (newfra_int > old_frame + 1) {
4441       printf("forward jump detected, frame %d!\n", newfra_int);
4442     }
4443     fflush(stdout);
4444     old_frame = newfra_int;
4445 #endif
4446   }
4447   else {
4448     if (sync) {
4449       /* Try to keep the playback in realtime by dropping frames. */
4450 
4451       /* How much time (in frames) has passed since the last frame was drawn? */
4452       double delta_frames = wt->delta * FPS;
4453 
4454       /* Add the remaining fraction from the last time step. */
4455       delta_frames += sad->lagging_frame_count;
4456 
4457       if (delta_frames < 1.0) {
4458         /* We can render faster than the scene frame rate. However skipping or delaying frames
4459          * here seems to in practice lead to jittery playback so just step forward a minimum of
4460          * one frame. (Even though this can lead to too fast playback, the jitteryness is more
4461          * annoying)
4462          */
4463         delta_frames = 1.0f;
4464         sad->lagging_frame_count = 0;
4465       }
4466       else {
4467         /* Extract the delta frame fractions that will be skipped when converting to int. */
4468         sad->lagging_frame_count = delta_frames - (int)delta_frames;
4469       }
4470 
4471       const int step = delta_frames;
4472 
4473       /* skip frames */
4474       if (sad->flag & ANIMPLAY_FLAG_REVERSE) {
4475         scene->r.cfra -= step;
4476       }
4477       else {
4478         scene->r.cfra += step;
4479       }
4480     }
4481     else {
4482       /* one frame +/- */
4483       if (sad->flag & ANIMPLAY_FLAG_REVERSE) {
4484         scene->r.cfra--;
4485       }
4486       else {
4487         scene->r.cfra++;
4488       }
4489     }
4490   }
4491 
4492   /* reset 'jumped' flag before checking if we need to jump... */
4493   sad->flag &= ~ANIMPLAY_FLAG_JUMPED;
4494 
4495   if (sad->flag & ANIMPLAY_FLAG_REVERSE) {
4496     /* jump back to end? */
4497     if (PRVRANGEON) {
4498       if (scene->r.cfra < scene->r.psfra) {
4499         scene->r.cfra = scene->r.pefra;
4500         sad->flag |= ANIMPLAY_FLAG_JUMPED;
4501       }
4502     }
4503     else {
4504       if (scene->r.cfra < scene->r.sfra) {
4505         scene->r.cfra = scene->r.efra;
4506         sad->flag |= ANIMPLAY_FLAG_JUMPED;
4507       }
4508     }
4509   }
4510   else {
4511     /* jump back to start? */
4512     if (PRVRANGEON) {
4513       if (scene->r.cfra > scene->r.pefra) {
4514         scene->r.cfra = scene->r.psfra;
4515         sad->flag |= ANIMPLAY_FLAG_JUMPED;
4516       }
4517     }
4518     else {
4519       if (scene->r.cfra > scene->r.efra) {
4520         scene->r.cfra = scene->r.sfra;
4521         sad->flag |= ANIMPLAY_FLAG_JUMPED;
4522       }
4523     }
4524   }
4525 
4526   /* next frame overridden by user action (pressed jump to first/last frame) */
4527   if (sad->flag & ANIMPLAY_FLAG_USE_NEXT_FRAME) {
4528     scene->r.cfra = sad->nextfra;
4529     sad->flag &= ~ANIMPLAY_FLAG_USE_NEXT_FRAME;
4530     sad->flag |= ANIMPLAY_FLAG_JUMPED;
4531   }
4532 
4533   if (sad->flag & ANIMPLAY_FLAG_JUMPED) {
4534     DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
4535 #ifdef PROFILE_AUDIO_SYNCH
4536     old_frame = CFRA;
4537 #endif
4538   }
4539 
4540   /* since we follow drawflags, we can't send notifier but tag regions ourselves */
4541   if (depsgraph != NULL) {
4542     ED_update_for_newframe(bmain, depsgraph);
4543   }
4544 
4545   LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
4546     const bScreen *win_screen = WM_window_get_active_screen(window);
4547 
4548     LISTBASE_FOREACH (ScrArea *, area, &win_screen->areabase) {
4549       LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
4550         bool redraw = false;
4551         if (region == sad->region) {
4552           redraw = true;
4553         }
4554         else if (match_region_with_redraws(
4555                      area, region->regiontype, sad->redraws, sad->from_anim_edit)) {
4556           redraw = true;
4557         }
4558 
4559         if (redraw) {
4560           screen_animation_region_tag_redraw(area, region, scene, sad->redraws);
4561         }
4562       }
4563     }
4564   }
4565 
4566   /* update frame rate info too
4567    * NOTE: this may not be accurate enough, since we might need this after modifiers/etc.
4568    * have been calculated instead of just before updates have been done?
4569    */
4570   ED_refresh_viewport_fps(C);
4571 
4572   /* Recalculate the time-step for the timer now that we've finished calculating this,
4573    * since the frames-per-second value may have been changed.
4574    */
4575   /* TODO: this may make evaluation a bit slower if the value doesn't change...
4576    * any way to avoid this? */
4577   wt->timestep = (1.0 / FPS);
4578 
4579   return OPERATOR_FINISHED;
4580 }
4581 
SCREEN_OT_animation_step(wmOperatorType * ot)4582 static void SCREEN_OT_animation_step(wmOperatorType *ot)
4583 {
4584   /* identifiers */
4585   ot->name = "Animation Step";
4586   ot->description = "Step through animation by position";
4587   ot->idname = "SCREEN_OT_animation_step";
4588 
4589   /* api callbacks */
4590   ot->invoke = screen_animation_step_invoke;
4591 
4592   ot->poll = ED_operator_screenactive_norender;
4593 }
4594 
4595 /** \} */
4596 
4597 /* -------------------------------------------------------------------- */
4598 /** \name Animation Playback Operator
4599  *
4600  * Animation Playback with Timer.
4601  * \{ */
4602 
4603 /* find window that owns the animation timer */
ED_screen_animation_playing(const wmWindowManager * wm)4604 bScreen *ED_screen_animation_playing(const wmWindowManager *wm)
4605 {
4606   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
4607     bScreen *screen = WM_window_get_active_screen(win);
4608 
4609     if (screen->animtimer || screen->scrubbing) {
4610       return screen;
4611     }
4612   }
4613 
4614   return NULL;
4615 }
4616 
ED_screen_animation_no_scrub(const wmWindowManager * wm)4617 bScreen *ED_screen_animation_no_scrub(const wmWindowManager *wm)
4618 {
4619   LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
4620     bScreen *screen = WM_window_get_active_screen(win);
4621 
4622     if (screen->animtimer) {
4623       return screen;
4624     }
4625   }
4626 
4627   return NULL;
4628 }
4629 
4630 /* toggle operator */
ED_screen_animation_play(bContext * C,int sync,int mode)4631 int ED_screen_animation_play(bContext *C, int sync, int mode)
4632 {
4633   bScreen *screen = CTX_wm_screen(C);
4634   Scene *scene = CTX_data_scene(C);
4635   Scene *scene_eval = DEG_get_evaluated_scene(CTX_data_ensure_evaluated_depsgraph(C));
4636 
4637   if (ED_screen_animation_playing(CTX_wm_manager(C))) {
4638     /* stop playback now */
4639     ED_screen_animation_timer(C, 0, 0, 0);
4640     BKE_sound_stop_scene(scene_eval);
4641 
4642     WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
4643   }
4644   else {
4645     /* these settings are currently only available from a menu in the TimeLine */
4646     if (mode == 1) { /* XXX only play audio forwards!? */
4647       BKE_sound_play_scene(scene_eval);
4648     }
4649 
4650     ED_screen_animation_timer(C, screen->redraws_flag, sync, mode);
4651 
4652     if (screen->animtimer) {
4653       wmTimer *wt = screen->animtimer;
4654       ScreenAnimData *sad = wt->customdata;
4655 
4656       sad->region = CTX_wm_region(C);
4657     }
4658   }
4659 
4660   return OPERATOR_FINISHED;
4661 }
4662 
screen_animation_play_exec(bContext * C,wmOperator * op)4663 static int screen_animation_play_exec(bContext *C, wmOperator *op)
4664 {
4665   int mode = (RNA_boolean_get(op->ptr, "reverse")) ? -1 : 1;
4666   int sync = -1;
4667 
4668   if (RNA_struct_property_is_set(op->ptr, "sync")) {
4669     sync = (RNA_boolean_get(op->ptr, "sync"));
4670   }
4671 
4672   return ED_screen_animation_play(C, sync, mode);
4673 }
4674 
SCREEN_OT_animation_play(wmOperatorType * ot)4675 static void SCREEN_OT_animation_play(wmOperatorType *ot)
4676 {
4677   PropertyRNA *prop;
4678 
4679   /* identifiers */
4680   ot->name = "Play Animation";
4681   ot->description = "Play animation";
4682   ot->idname = "SCREEN_OT_animation_play";
4683 
4684   /* api callbacks */
4685   ot->exec = screen_animation_play_exec;
4686 
4687   ot->poll = ED_operator_screenactive_norender;
4688 
4689   prop = RNA_def_boolean(
4690       ot->srna, "reverse", 0, "Play in Reverse", "Animation is played backwards");
4691   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4692   prop = RNA_def_boolean(ot->srna, "sync", 0, "Sync", "Drop frames to maintain framerate");
4693   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4694 }
4695 
4696 /** \} */
4697 
4698 /* -------------------------------------------------------------------- */
4699 /** \name Animation Cancel Operator
4700  * \{ */
4701 
screen_animation_cancel_exec(bContext * C,wmOperator * op)4702 static int screen_animation_cancel_exec(bContext *C, wmOperator *op)
4703 {
4704   bScreen *screen = ED_screen_animation_playing(CTX_wm_manager(C));
4705 
4706   if (screen) {
4707     if (RNA_boolean_get(op->ptr, "restore_frame") && screen->animtimer) {
4708       ScreenAnimData *sad = screen->animtimer->customdata;
4709       Scene *scene = CTX_data_scene(C);
4710 
4711       /* reset current frame before stopping, and just send a notifier to deal with the rest
4712        * (since playback still needs to be stopped)
4713        */
4714       scene->r.cfra = sad->sfra;
4715 
4716       WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
4717     }
4718 
4719     /* call the other "toggling" operator to clean up now */
4720     ED_screen_animation_play(C, 0, 0);
4721   }
4722 
4723   return OPERATOR_PASS_THROUGH;
4724 }
4725 
SCREEN_OT_animation_cancel(wmOperatorType * ot)4726 static void SCREEN_OT_animation_cancel(wmOperatorType *ot)
4727 {
4728   /* identifiers */
4729   ot->name = "Cancel Animation";
4730   ot->description = "Cancel animation, returning to the original frame";
4731   ot->idname = "SCREEN_OT_animation_cancel";
4732 
4733   /* api callbacks */
4734   ot->exec = screen_animation_cancel_exec;
4735 
4736   ot->poll = ED_operator_screenactive;
4737 
4738   RNA_def_boolean(ot->srna,
4739                   "restore_frame",
4740                   true,
4741                   "Restore Frame",
4742                   "Restore the frame when animation was initialized");
4743 }
4744 
4745 /** \} */
4746 
4747 /* -------------------------------------------------------------------- */
4748 /** \name Box Select Operator (Template)
4749  * \{ */
4750 
4751 /* operator state vars used: (added by default WM callbacks)
4752  * xmin, ymin
4753  * xmax, ymax
4754  *
4755  * customdata: the wmGesture pointer
4756  *
4757  * callbacks:
4758  *
4759  * exec()   has to be filled in by user
4760  *
4761  * invoke() default WM function
4762  * adds modal handler
4763  *
4764  * modal()  default WM function
4765  * accept modal events while doing it, calls exec(), handles ESC and border drawing
4766  *
4767  * poll()   has to be filled in by user for context
4768  */
4769 #if 0
4770 static int box_select_exec(bContext *C, wmOperator *op)
4771 {
4772   int event_type = RNA_int_get(op->ptr, "event_type");
4773 
4774   if (event_type == LEFTMOUSE) {
4775     printf("box select do select\n");
4776   }
4777   else if (event_type == RIGHTMOUSE) {
4778     printf("box select deselect\n");
4779   }
4780   else {
4781     printf("box select do something\n");
4782   }
4783 
4784   return 1;
4785 }
4786 
4787 static void SCREEN_OT_box_select(wmOperatorType *ot)
4788 {
4789   /* identifiers */
4790   ot->name = "Box Select";
4791   ot->idname = "SCREEN_OT_box_select";
4792 
4793   /* api callbacks */
4794   ot->exec = box_select_exec;
4795   ot->invoke = WM_gesture_box_invoke;
4796   ot->modal = WM_gesture_box_modal;
4797   ot->cancel = WM_gesture_box_cancel;
4798 
4799   ot->poll = ED_operator_areaactive;
4800 
4801   /* rna */
4802   RNA_def_int(ot->srna, "event_type", 0, INT_MIN, INT_MAX, "Event Type", "", INT_MIN, INT_MAX);
4803   WM_operator_properties_border(ot);
4804 }
4805 #endif
4806 
4807 /** \} */
4808 
4809 /* -------------------------------------------------------------------- */
4810 /** \name Full Screen Back Operator
4811  *
4812  * Use for generic full-screen 'back' button.
4813  * \{ */
4814 
fullscreen_back_exec(bContext * C,wmOperator * op)4815 static int fullscreen_back_exec(bContext *C, wmOperator *op)
4816 {
4817   bScreen *screen = CTX_wm_screen(C);
4818   ScrArea *area = NULL;
4819 
4820   /* search current screen for 'fullscreen' areas */
4821   LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) {
4822     if (area_iter->full) {
4823       area = area_iter;
4824       break;
4825     }
4826   }
4827   if (!area) {
4828     BKE_report(op->reports, RPT_ERROR, "No fullscreen areas were found");
4829     return OPERATOR_CANCELLED;
4830   }
4831 
4832   ED_screen_full_prevspace(C, area);
4833 
4834   return OPERATOR_FINISHED;
4835 }
4836 
SCREEN_OT_back_to_previous(struct wmOperatorType * ot)4837 static void SCREEN_OT_back_to_previous(struct wmOperatorType *ot)
4838 {
4839   /* identifiers */
4840   ot->name = "Back to Previous Screen";
4841   ot->description = "Revert back to the original screen layout, before fullscreen area overlay";
4842   ot->idname = "SCREEN_OT_back_to_previous";
4843 
4844   /* api callbacks */
4845   ot->exec = fullscreen_back_exec;
4846   ot->poll = ED_operator_screenactive;
4847 }
4848 
4849 /** \} */
4850 
4851 /* -------------------------------------------------------------------- */
4852 /** \name Show User Preferences Operator
4853  * \{ */
4854 
userpref_show_exec(bContext * C,wmOperator * op)4855 static int userpref_show_exec(bContext *C, wmOperator *op)
4856 {
4857   wmWindow *win_cur = CTX_wm_window(C);
4858   /* Use eventstate, not event from _invoke, so this can be called through exec(). */
4859   const wmEvent *event = win_cur->eventstate;
4860   int sizex = (500 + UI_NAVIGATION_REGION_WIDTH) * UI_DPI_FAC;
4861   int sizey = 520 * UI_DPI_FAC;
4862 
4863   /* changes context! */
4864   if (WM_window_open_temp(C,
4865                           IFACE_("Blender Preferences"),
4866                           event->x,
4867                           event->y,
4868                           sizex,
4869                           sizey,
4870                           SPACE_USERPREF,
4871                           false) != NULL) {
4872     /* The header only contains the editor switcher and looks empty.
4873      * So hiding in the temp window makes sense. */
4874     ScrArea *area = CTX_wm_area(C);
4875     ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
4876 
4877     region->flag |= RGN_FLAG_HIDDEN;
4878     ED_region_visibility_change_update(C, area, region);
4879 
4880     return OPERATOR_FINISHED;
4881   }
4882   BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
4883   return OPERATOR_CANCELLED;
4884 }
4885 
SCREEN_OT_userpref_show(struct wmOperatorType * ot)4886 static void SCREEN_OT_userpref_show(struct wmOperatorType *ot)
4887 {
4888   /* identifiers */
4889   ot->name = "Show Preferences";
4890   ot->description = "Edit user preferences and system settings";
4891   ot->idname = "SCREEN_OT_userpref_show";
4892 
4893   /* api callbacks */
4894   ot->exec = userpref_show_exec;
4895   ot->poll = ED_operator_screenactive;
4896 }
4897 
4898 /** \} */
4899 
4900 /* -------------------------------------------------------------------- */
4901 /** \name Show Drivers Editor Operator
4902  * \{ */
4903 
drivers_editor_show_exec(bContext * C,wmOperator * op)4904 static int drivers_editor_show_exec(bContext *C, wmOperator *op)
4905 {
4906   wmWindow *win_cur = CTX_wm_window(C);
4907   /* Use eventstate, not event from _invoke, so this can be called through exec(). */
4908   const wmEvent *event = win_cur->eventstate;
4909 
4910   int sizex = 900 * UI_DPI_FAC;
4911   int sizey = 580 * UI_DPI_FAC;
4912 
4913   /* Get active property to show driver for
4914    * - Need to grab it first, or else this info disappears
4915    *   after we've created the window
4916    */
4917   int index;
4918   PointerRNA ptr;
4919   PropertyRNA *prop;
4920   uiBut *but = UI_context_active_but_prop_get(C, &ptr, &prop, &index);
4921 
4922   /* changes context! */
4923   if (WM_window_open_temp(C,
4924                           IFACE_("Blender Drivers Editor"),
4925                           event->x,
4926                           event->y,
4927                           sizex,
4928                           sizey,
4929                           SPACE_GRAPH,
4930                           false) != NULL) {
4931     ED_drivers_editor_init(C, CTX_wm_area(C));
4932 
4933     /* activate driver F-Curve for the property under the cursor */
4934     if (but) {
4935       bool driven, special;
4936       FCurve *fcu = BKE_fcurve_find_by_rna_context_ui(
4937           C, &ptr, prop, index, NULL, NULL, &driven, &special);
4938 
4939       if (fcu) {
4940         /* Isolate this F-Curve... */
4941         bAnimContext ac;
4942         if (ANIM_animdata_get_context(C, &ac)) {
4943           int filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS;
4944           ANIM_anim_channels_select_set(&ac, ACHANNEL_SETFLAG_CLEAR);
4945           ANIM_set_active_channel(&ac, ac.data, ac.datatype, filter, fcu, ANIMTYPE_FCURVE);
4946         }
4947         else {
4948           /* Just blindly isolate...
4949            * This isn't the best, and shouldn't happen, but may be enough. */
4950           fcu->flag |= (FCURVE_ACTIVE | FCURVE_SELECTED);
4951         }
4952       }
4953     }
4954 
4955     return OPERATOR_FINISHED;
4956   }
4957   BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
4958   return OPERATOR_CANCELLED;
4959 }
4960 
SCREEN_OT_drivers_editor_show(struct wmOperatorType * ot)4961 static void SCREEN_OT_drivers_editor_show(struct wmOperatorType *ot)
4962 {
4963   /* identifiers */
4964   ot->name = "Show Drivers Editor";
4965   ot->description = "Show drivers editor in a separate window";
4966   ot->idname = "SCREEN_OT_drivers_editor_show";
4967 
4968   /* api callbacks */
4969   ot->exec = drivers_editor_show_exec;
4970   ot->poll = ED_operator_screenactive;
4971 }
4972 
4973 /** \} */
4974 
4975 /* -------------------------------------------------------------------- */
4976 /** \name Show Info Log Operator
4977  * \{ */
4978 
info_log_show_exec(bContext * C,wmOperator * op)4979 static int info_log_show_exec(bContext *C, wmOperator *op)
4980 {
4981   wmWindow *win_cur = CTX_wm_window(C);
4982   /* Use eventstate, not event from _invoke, so this can be called through exec(). */
4983   const wmEvent *event = win_cur->eventstate;
4984   int sizex = 900 * UI_DPI_FAC;
4985   int sizey = 580 * UI_DPI_FAC;
4986   int shift_y = 480;
4987 
4988   /* changes context! */
4989   if (WM_window_open_temp(C,
4990                           IFACE_("Blender Info Log"),
4991                           event->x,
4992                           event->y + shift_y,
4993                           sizex,
4994                           sizey,
4995                           SPACE_INFO,
4996                           false) != NULL) {
4997     return OPERATOR_FINISHED;
4998   }
4999   BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
5000   return OPERATOR_CANCELLED;
5001 }
5002 
SCREEN_OT_info_log_show(struct wmOperatorType * ot)5003 static void SCREEN_OT_info_log_show(struct wmOperatorType *ot)
5004 {
5005   /* identifiers */
5006   ot->name = "Show Info Log";
5007   ot->description = "Show info log in a separate window";
5008   ot->idname = "SCREEN_OT_info_log_show";
5009 
5010   /* api callbacks */
5011   ot->exec = info_log_show_exec;
5012   ot->poll = ED_operator_screenactive;
5013 }
5014 
5015 /** \} */
5016 
5017 /* -------------------------------------------------------------------- */
5018 /** \name New Screen Operator
5019  * \{ */
5020 
screen_new_exec(bContext * C,wmOperator * UNUSED (op))5021 static int screen_new_exec(bContext *C, wmOperator *UNUSED(op))
5022 {
5023   Main *bmain = CTX_data_main(C);
5024   wmWindow *win = CTX_wm_window(C);
5025   WorkSpace *workspace = BKE_workspace_active_get(win->workspace_hook);
5026   WorkSpaceLayout *layout_old = BKE_workspace_active_layout_get(win->workspace_hook);
5027 
5028   WorkSpaceLayout *layout_new = ED_workspace_layout_duplicate(bmain, workspace, layout_old, win);
5029 
5030   WM_event_add_notifier(C, NC_SCREEN | ND_LAYOUTBROWSE, layout_new);
5031 
5032   return OPERATOR_FINISHED;
5033 }
5034 
SCREEN_OT_new(wmOperatorType * ot)5035 static void SCREEN_OT_new(wmOperatorType *ot)
5036 {
5037   /* identifiers */
5038   ot->name = "New Screen";
5039   ot->description = "Add a new screen";
5040   ot->idname = "SCREEN_OT_new";
5041 
5042   /* api callbacks */
5043   ot->exec = screen_new_exec;
5044   ot->poll = WM_operator_winactive;
5045 }
5046 
5047 /** \} */
5048 
5049 /* -------------------------------------------------------------------- */
5050 /** \name Delete Screen Operator
5051  * \{ */
5052 
screen_delete_exec(bContext * C,wmOperator * UNUSED (op))5053 static int screen_delete_exec(bContext *C, wmOperator *UNUSED(op))
5054 {
5055   bScreen *screen = CTX_wm_screen(C);
5056   WorkSpace *workspace = CTX_wm_workspace(C);
5057   WorkSpaceLayout *layout = BKE_workspace_layout_find(workspace, screen);
5058 
5059   WM_event_add_notifier(C, NC_SCREEN | ND_LAYOUTDELETE, layout);
5060 
5061   return OPERATOR_FINISHED;
5062 }
5063 
SCREEN_OT_delete(wmOperatorType * ot)5064 static void SCREEN_OT_delete(wmOperatorType *ot)
5065 {
5066   /* identifiers */
5067   ot->name = "Delete Screen";
5068   ot->description = "Delete active screen";
5069   ot->idname = "SCREEN_OT_delete";
5070 
5071   /* api callbacks */
5072   ot->exec = screen_delete_exec;
5073 }
5074 
5075 /** \} */
5076 
5077 /* -------------------------------------------------------------------- */
5078 /** \name Region Alpha Blending Operator
5079  *
5080  * Implementation note: a disappearing region needs at least 1 last draw with
5081  * 100% backbuffer texture over it - then triple buffer will clear it entirely.
5082  * This because flag #RGN_FLAG_HIDDEN is set in end - region doesn't draw at all then.
5083  *
5084  * \{ */
5085 
5086 typedef struct RegionAlphaInfo {
5087   ScrArea *area;
5088   ARegion *region, *child_region; /* other region */
5089   int hidden;
5090 } RegionAlphaInfo;
5091 
5092 #define TIMEOUT 0.1f
5093 #define TIMESTEP (1.0f / 60.0f)
5094 
ED_region_blend_alpha(ARegion * region)5095 float ED_region_blend_alpha(ARegion *region)
5096 {
5097   /* check parent too */
5098   if (region->regiontimer == NULL && (region->alignment & RGN_SPLIT_PREV) && region->prev) {
5099     region = region->prev;
5100   }
5101 
5102   if (region->regiontimer) {
5103     RegionAlphaInfo *rgi = region->regiontimer->customdata;
5104     float alpha;
5105 
5106     alpha = (float)region->regiontimer->duration / TIMEOUT;
5107     /* makes sure the blend out works 100% - without area redraws */
5108     if (rgi->hidden) {
5109       alpha = 0.9f - TIMESTEP - alpha;
5110     }
5111 
5112     CLAMP(alpha, 0.0f, 1.0f);
5113     return alpha;
5114   }
5115   return 1.0f;
5116 }
5117 
5118 /* assumes region has running region-blend timer */
region_blend_end(bContext * C,ARegion * region,const bool is_running)5119 static void region_blend_end(bContext *C, ARegion *region, const bool is_running)
5120 {
5121   RegionAlphaInfo *rgi = region->regiontimer->customdata;
5122 
5123   /* always send redraw */
5124   ED_region_tag_redraw(region);
5125   if (rgi->child_region) {
5126     ED_region_tag_redraw(rgi->child_region);
5127   }
5128 
5129   /* if running timer was hiding, the flag toggle went wrong */
5130   if (is_running) {
5131     if (rgi->hidden) {
5132       rgi->region->flag &= ~RGN_FLAG_HIDDEN;
5133     }
5134   }
5135   else {
5136     if (rgi->hidden) {
5137       rgi->region->flag |= rgi->hidden;
5138       ED_area_init(CTX_wm_manager(C), CTX_wm_window(C), rgi->area);
5139     }
5140     /* area decoration needs redraw in end */
5141     ED_area_tag_redraw(rgi->area);
5142   }
5143   WM_event_remove_timer(CTX_wm_manager(C), NULL, region->regiontimer); /* frees rgi */
5144   region->regiontimer = NULL;
5145 }
5146 /**
5147  * \note Assumes that \a region itself is not a split version from previous region.
5148  */
ED_region_visibility_change_update_animated(bContext * C,ScrArea * area,ARegion * region)5149 void ED_region_visibility_change_update_animated(bContext *C, ScrArea *area, ARegion *region)
5150 {
5151   wmWindowManager *wm = CTX_wm_manager(C);
5152   wmWindow *win = CTX_wm_window(C);
5153 
5154   /* end running timer */
5155   if (region->regiontimer) {
5156 
5157     region_blend_end(C, region, true);
5158   }
5159   RegionAlphaInfo *rgi = MEM_callocN(sizeof(RegionAlphaInfo), "RegionAlphaInfo");
5160 
5161   rgi->hidden = region->flag & RGN_FLAG_HIDDEN;
5162   rgi->area = area;
5163   rgi->region = region;
5164   region->flag &= ~RGN_FLAG_HIDDEN;
5165 
5166   /* blend in, reinitialize regions because it got unhidden */
5167   if (rgi->hidden == 0) {
5168     ED_area_init(wm, win, area);
5169   }
5170   else {
5171     WM_event_remove_handlers(C, &region->handlers);
5172   }
5173 
5174   if (region->next) {
5175     if (region->next->alignment & RGN_SPLIT_PREV) {
5176       rgi->child_region = region->next;
5177     }
5178   }
5179 
5180   /* new timer */
5181   region->regiontimer = WM_event_add_timer(wm, win, TIMERREGION, TIMESTEP);
5182   region->regiontimer->customdata = rgi;
5183 }
5184 
5185 /* timer runs in win->handlers, so it cannot use context to find area/region */
region_blend_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * event)5186 static int region_blend_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
5187 {
5188   wmTimer *timer = event->customdata;
5189 
5190   /* event type is TIMERREGION, but we better check */
5191   if (event->type != TIMERREGION || timer == NULL) {
5192     return OPERATOR_PASS_THROUGH;
5193   }
5194 
5195   RegionAlphaInfo *rgi = timer->customdata;
5196 
5197   /* always send redraws */
5198   ED_region_tag_redraw(rgi->region);
5199   if (rgi->child_region) {
5200     ED_region_tag_redraw(rgi->child_region);
5201   }
5202 
5203   /* end timer? */
5204   if (rgi->region->regiontimer->duration > (double)TIMEOUT) {
5205     region_blend_end(C, rgi->region, false);
5206     return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
5207   }
5208 
5209   return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
5210 }
5211 
SCREEN_OT_region_blend(wmOperatorType * ot)5212 static void SCREEN_OT_region_blend(wmOperatorType *ot)
5213 {
5214   /* identifiers */
5215   ot->name = "Region Alpha";
5216   ot->idname = "SCREEN_OT_region_blend";
5217   ot->description = "Blend in and out overlapping region";
5218 
5219   /* api callbacks */
5220   ot->invoke = region_blend_invoke;
5221 
5222   /* flags */
5223   ot->flag = OPTYPE_INTERNAL;
5224 
5225   /* properties */
5226 }
5227 
5228 /** \} */
5229 
5230 /* -------------------------------------------------------------------- */
5231 /** \name Space Type Set or Cycle Operator
5232  * \{ */
5233 
space_type_set_or_cycle_poll(bContext * C)5234 static bool space_type_set_or_cycle_poll(bContext *C)
5235 {
5236   ScrArea *area = CTX_wm_area(C);
5237   return (area && !ELEM(area->spacetype, SPACE_TOPBAR, SPACE_STATUSBAR));
5238 }
5239 
space_type_set_or_cycle_exec(bContext * C,wmOperator * op)5240 static int space_type_set_or_cycle_exec(bContext *C, wmOperator *op)
5241 {
5242   const int space_type = RNA_enum_get(op->ptr, "space_type");
5243 
5244   PointerRNA ptr;
5245   ScrArea *area = CTX_wm_area(C);
5246   RNA_pointer_create((ID *)CTX_wm_screen(C), &RNA_Area, area, &ptr);
5247   PropertyRNA *prop_type = RNA_struct_find_property(&ptr, "type");
5248   PropertyRNA *prop_ui_type = RNA_struct_find_property(&ptr, "ui_type");
5249 
5250   if (area->spacetype != space_type) {
5251     /* Set the type. */
5252     RNA_property_enum_set(&ptr, prop_type, space_type);
5253     RNA_property_update(C, &ptr, prop_type);
5254   }
5255   else {
5256     /* Types match, cycle the subtype. */
5257     const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type);
5258     const EnumPropertyItem *item;
5259     int item_len;
5260     bool free;
5261     RNA_property_enum_items(C, &ptr, prop_ui_type, &item, &item_len, &free);
5262     int index = RNA_enum_from_value(item, space_type_ui);
5263     for (int i = 1; i < item_len; i++) {
5264       const EnumPropertyItem *item_test = &item[(index + i) % item_len];
5265       if ((item_test->value >> 16) == space_type) {
5266         RNA_property_enum_set(&ptr, prop_ui_type, item_test->value);
5267         RNA_property_update(C, &ptr, prop_ui_type);
5268         break;
5269       }
5270     }
5271     if (free) {
5272       MEM_freeN((void *)item);
5273     }
5274   }
5275 
5276   return OPERATOR_FINISHED;
5277 }
5278 
SCREEN_OT_space_type_set_or_cycle(wmOperatorType * ot)5279 static void SCREEN_OT_space_type_set_or_cycle(wmOperatorType *ot)
5280 {
5281   /* identifiers */
5282   ot->name = "Cycle Space Type Set";
5283   ot->description = "Set the space type or cycle sub-type";
5284   ot->idname = "SCREEN_OT_space_type_set_or_cycle";
5285 
5286   /* api callbacks */
5287   ot->exec = space_type_set_or_cycle_exec;
5288   ot->poll = space_type_set_or_cycle_poll;
5289 
5290   ot->flag = 0;
5291 
5292   RNA_def_enum(ot->srna, "space_type", rna_enum_space_type_items, SPACE_EMPTY, "Type", "");
5293 }
5294 
5295 /** \} */
5296 
5297 /* -------------------------------------------------------------------- */
5298 /** \name Space Context Cycle Operator
5299  * \{ */
5300 
5301 static const EnumPropertyItem space_context_cycle_direction[] = {
5302     {SPACE_CONTEXT_CYCLE_PREV, "PREV", 0, "Previous", ""},
5303     {SPACE_CONTEXT_CYCLE_NEXT, "NEXT", 0, "Next", ""},
5304     {0, NULL, 0, NULL, NULL},
5305 };
5306 
space_context_cycle_poll(bContext * C)5307 static bool space_context_cycle_poll(bContext *C)
5308 {
5309   ScrArea *area = CTX_wm_area(C);
5310   /* area might be NULL if called out of window bounds */
5311   return (area && ELEM(area->spacetype, SPACE_PROPERTIES, SPACE_USERPREF));
5312 }
5313 
5314 /**
5315  * Helper to get the correct RNA pointer/property pair for changing
5316  * the display context of active space type in \a area.
5317  */
context_cycle_prop_get(bScreen * screen,const ScrArea * area,PointerRNA * r_ptr,PropertyRNA ** r_prop)5318 static void context_cycle_prop_get(bScreen *screen,
5319                                    const ScrArea *area,
5320                                    PointerRNA *r_ptr,
5321                                    PropertyRNA **r_prop)
5322 {
5323   const char *propname;
5324 
5325   switch (area->spacetype) {
5326     case SPACE_PROPERTIES:
5327       RNA_pointer_create(&screen->id, &RNA_SpaceProperties, area->spacedata.first, r_ptr);
5328       propname = "context";
5329       break;
5330     case SPACE_USERPREF:
5331       RNA_pointer_create(NULL, &RNA_Preferences, &U, r_ptr);
5332       propname = "active_section";
5333       break;
5334     default:
5335       BLI_assert(0);
5336       propname = "";
5337   }
5338 
5339   *r_prop = RNA_struct_find_property(r_ptr, propname);
5340 }
5341 
space_context_cycle_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))5342 static int space_context_cycle_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
5343 {
5344   const int direction = RNA_enum_get(op->ptr, "direction");
5345 
5346   PointerRNA ptr;
5347   PropertyRNA *prop;
5348   context_cycle_prop_get(CTX_wm_screen(C), CTX_wm_area(C), &ptr, &prop);
5349   const int old_context = RNA_property_enum_get(&ptr, prop);
5350   const int new_context = RNA_property_enum_step(
5351       C, &ptr, prop, old_context, direction == SPACE_CONTEXT_CYCLE_PREV ? -1 : 1);
5352   RNA_property_enum_set(&ptr, prop, new_context);
5353   RNA_property_update(C, &ptr, prop);
5354 
5355   return OPERATOR_FINISHED;
5356 }
5357 
SCREEN_OT_space_context_cycle(wmOperatorType * ot)5358 static void SCREEN_OT_space_context_cycle(wmOperatorType *ot)
5359 {
5360   /* identifiers */
5361   ot->name = "Cycle Space Context";
5362   ot->description = "Cycle through the editor context by activating the next/previous one";
5363   ot->idname = "SCREEN_OT_space_context_cycle";
5364 
5365   /* api callbacks */
5366   ot->invoke = space_context_cycle_invoke;
5367   ot->poll = space_context_cycle_poll;
5368 
5369   ot->flag = 0;
5370 
5371   RNA_def_enum(ot->srna,
5372                "direction",
5373                space_context_cycle_direction,
5374                SPACE_CONTEXT_CYCLE_NEXT,
5375                "Direction",
5376                "Direction to cycle through");
5377 }
5378 
5379 /** \} */
5380 
5381 /* -------------------------------------------------------------------- */
5382 /** \name Workspace Cycle Operator
5383  * \{ */
5384 
space_workspace_cycle_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))5385 static int space_workspace_cycle_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
5386 {
5387   wmWindow *win = CTX_wm_window(C);
5388   if (WM_window_is_temp_screen(win)) {
5389     return OPERATOR_CANCELLED;
5390   }
5391 
5392   Main *bmain = CTX_data_main(C);
5393   const int direction = RNA_enum_get(op->ptr, "direction");
5394   WorkSpace *workspace_src = WM_window_get_active_workspace(win);
5395   WorkSpace *workspace_dst = NULL;
5396 
5397   ListBase ordered;
5398   BKE_id_ordered_list(&ordered, &bmain->workspaces);
5399 
5400   LISTBASE_FOREACH (LinkData *, link, &ordered) {
5401     if (link->data == workspace_src) {
5402       if (direction == SPACE_CONTEXT_CYCLE_PREV) {
5403         workspace_dst = (link->prev) ? link->prev->data : NULL;
5404       }
5405       else {
5406         workspace_dst = (link->next) ? link->next->data : NULL;
5407       }
5408     }
5409   }
5410 
5411   if (workspace_dst == NULL) {
5412     LinkData *link = (direction == SPACE_CONTEXT_CYCLE_PREV) ? ordered.last : ordered.first;
5413     workspace_dst = link->data;
5414   }
5415 
5416   BLI_freelistN(&ordered);
5417 
5418   if (workspace_src == workspace_dst) {
5419     return OPERATOR_CANCELLED;
5420   }
5421 
5422   win->workspace_hook->temp_workspace_store = workspace_dst;
5423   WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_SET, workspace_dst);
5424   win->workspace_hook->temp_workspace_store = NULL;
5425 
5426   return OPERATOR_FINISHED;
5427 }
5428 
SCREEN_OT_workspace_cycle(wmOperatorType * ot)5429 static void SCREEN_OT_workspace_cycle(wmOperatorType *ot)
5430 {
5431   /* identifiers */
5432   ot->name = "Cycle Workspace";
5433   ot->description = "Cycle through workspaces";
5434   ot->idname = "SCREEN_OT_workspace_cycle";
5435 
5436   /* api callbacks */
5437   ot->invoke = space_workspace_cycle_invoke;
5438   ot->poll = ED_operator_screenactive;
5439 
5440   ot->flag = 0;
5441 
5442   RNA_def_enum(ot->srna,
5443                "direction",
5444                space_context_cycle_direction,
5445                SPACE_CONTEXT_CYCLE_NEXT,
5446                "Direction",
5447                "Direction to cycle through");
5448 }
5449 
5450 /** \} */
5451 
5452 /* -------------------------------------------------------------------- */
5453 /** \name Assigning Operator Types
5454  * \{ */
5455 
5456 /* called in spacetypes.c */
ED_operatortypes_screen(void)5457 void ED_operatortypes_screen(void)
5458 {
5459   /* generic UI stuff */
5460   WM_operatortype_append(SCREEN_OT_actionzone);
5461   WM_operatortype_append(SCREEN_OT_repeat_last);
5462   WM_operatortype_append(SCREEN_OT_repeat_history);
5463   WM_operatortype_append(SCREEN_OT_redo_last);
5464 
5465   /* screen tools */
5466   WM_operatortype_append(SCREEN_OT_area_move);
5467   WM_operatortype_append(SCREEN_OT_area_split);
5468   WM_operatortype_append(SCREEN_OT_area_join);
5469   WM_operatortype_append(SCREEN_OT_area_options);
5470   WM_operatortype_append(SCREEN_OT_area_dupli);
5471   WM_operatortype_append(SCREEN_OT_area_swap);
5472   WM_operatortype_append(SCREEN_OT_region_quadview);
5473   WM_operatortype_append(SCREEN_OT_region_scale);
5474   WM_operatortype_append(SCREEN_OT_region_toggle);
5475   WM_operatortype_append(SCREEN_OT_region_flip);
5476   WM_operatortype_append(SCREEN_OT_header_toggle_menus);
5477   WM_operatortype_append(SCREEN_OT_region_context_menu);
5478   WM_operatortype_append(SCREEN_OT_screen_set);
5479   WM_operatortype_append(SCREEN_OT_screen_full_area);
5480   WM_operatortype_append(SCREEN_OT_back_to_previous);
5481   WM_operatortype_append(SCREEN_OT_spacedata_cleanup);
5482   WM_operatortype_append(SCREEN_OT_screenshot);
5483   WM_operatortype_append(SCREEN_OT_userpref_show);
5484   WM_operatortype_append(SCREEN_OT_drivers_editor_show);
5485   WM_operatortype_append(SCREEN_OT_info_log_show);
5486   WM_operatortype_append(SCREEN_OT_region_blend);
5487   WM_operatortype_append(SCREEN_OT_space_type_set_or_cycle);
5488   WM_operatortype_append(SCREEN_OT_space_context_cycle);
5489   WM_operatortype_append(SCREEN_OT_workspace_cycle);
5490 
5491   /*frame changes*/
5492   WM_operatortype_append(SCREEN_OT_frame_offset);
5493   WM_operatortype_append(SCREEN_OT_frame_jump);
5494   WM_operatortype_append(SCREEN_OT_keyframe_jump);
5495   WM_operatortype_append(SCREEN_OT_marker_jump);
5496 
5497   WM_operatortype_append(SCREEN_OT_animation_step);
5498   WM_operatortype_append(SCREEN_OT_animation_play);
5499   WM_operatortype_append(SCREEN_OT_animation_cancel);
5500 
5501   /* new/delete */
5502   WM_operatortype_append(SCREEN_OT_new);
5503   WM_operatortype_append(SCREEN_OT_delete);
5504 
5505   /* tools shared by more space types */
5506   WM_operatortype_append(ED_OT_undo);
5507   WM_operatortype_append(ED_OT_undo_push);
5508   WM_operatortype_append(ED_OT_redo);
5509   WM_operatortype_append(ED_OT_undo_redo);
5510   WM_operatortype_append(ED_OT_undo_history);
5511 
5512   WM_operatortype_append(ED_OT_flush_edits);
5513 }
5514 
5515 /** \} */
5516 
5517 /* -------------------------------------------------------------------- */
5518 /** \name Operator Key Map
5519  * \{ */
5520 
keymap_modal_set(wmKeyConfig * keyconf)5521 static void keymap_modal_set(wmKeyConfig *keyconf)
5522 {
5523   static const EnumPropertyItem modal_items[] = {
5524       {KM_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
5525       {KM_MODAL_APPLY, "APPLY", 0, "Apply", ""},
5526       {KM_MODAL_SNAP_ON, "SNAP", 0, "Snap on", ""},
5527       {KM_MODAL_SNAP_OFF, "SNAP_OFF", 0, "Snap off", ""},
5528       {0, NULL, 0, NULL, NULL},
5529   };
5530 
5531   /* Standard Modal keymap ------------------------------------------------ */
5532   wmKeyMap *keymap = WM_modalkeymap_ensure(keyconf, "Standard Modal Map", modal_items);
5533 
5534   WM_modalkeymap_assign(keymap, "SCREEN_OT_area_move");
5535 }
5536 
blend_file_drop_poll(bContext * UNUSED (C),wmDrag * drag,const wmEvent * UNUSED (event),const char ** UNUSED (r_tooltip))5537 static bool blend_file_drop_poll(bContext *UNUSED(C),
5538                                  wmDrag *drag,
5539                                  const wmEvent *UNUSED(event),
5540                                  const char **UNUSED(r_tooltip))
5541 {
5542   if (drag->type == WM_DRAG_PATH) {
5543     if (drag->icon == ICON_FILE_BLEND) {
5544       return true;
5545     }
5546   }
5547   return false;
5548 }
5549 
blend_file_drop_copy(wmDrag * drag,wmDropBox * drop)5550 static void blend_file_drop_copy(wmDrag *drag, wmDropBox *drop)
5551 {
5552   /* copy drag path to properties */
5553   RNA_string_set(drop->ptr, "filepath", drag->path);
5554 }
5555 
5556 /* called in spacetypes.c */
ED_keymap_screen(wmKeyConfig * keyconf)5557 void ED_keymap_screen(wmKeyConfig *keyconf)
5558 {
5559   /* Screen Editing ------------------------------------------------ */
5560   WM_keymap_ensure(keyconf, "Screen Editing", 0, 0);
5561 
5562   /* Header Editing ------------------------------------------------ */
5563   /* note: this is only used when the cursor is inside the header */
5564   WM_keymap_ensure(keyconf, "Header", 0, 0);
5565 
5566   /* Screen General ------------------------------------------------ */
5567   WM_keymap_ensure(keyconf, "Screen", 0, 0);
5568 
5569   /* Anim Playback ------------------------------------------------ */
5570   WM_keymap_ensure(keyconf, "Frames", 0, 0);
5571 
5572   /* dropbox for entire window */
5573   ListBase *lb = WM_dropboxmap_find("Window", 0, 0);
5574   WM_dropbox_add(lb, "WM_OT_drop_blend_file", blend_file_drop_poll, blend_file_drop_copy);
5575   WM_dropbox_add(lb, "UI_OT_drop_color", UI_drop_color_poll, UI_drop_color_copy);
5576 
5577   keymap_modal_set(keyconf);
5578 }
5579 
5580 /** \} */
5581