121db2fac993834418b474a500ab257bddda986e
[blender.git] / source / blender / editors / screen / screen_ops.c
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_math.h"
30 #include "BLI_blenlib.h"
31 #include "BLI_dlrbTree.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_lattice_types.h"
39 #include "DNA_object_types.h"
40 #include "DNA_curve_types.h"
41 #include "DNA_gpencil_types.h"
42 #include "DNA_scene_types.h"
43 #include "DNA_meta_types.h"
44 #include "DNA_mesh_types.h"
45 #include "DNA_mask_types.h"
46 #include "DNA_node_types.h"
47 #include "DNA_workspace_types.h"
48 #include "DNA_userdef_types.h"
49
50 #include "BKE_context.h"
51 #include "BKE_customdata.h"
52 #include "BKE_editmesh.h"
53 #include "BKE_fcurve.h"
54 #include "BKE_global.h"
55 #include "BKE_icons.h"
56 #include "BKE_library.h"
57 #include "BKE_main.h"
58 #include "BKE_mask.h"
59 #include "BKE_object.h"
60 #include "BKE_report.h"
61 #include "BKE_scene.h"
62 #include "BKE_screen.h"
63 #include "BKE_sound.h"
64 #include "BKE_workspace.h"
65
66 #include "WM_api.h"
67 #include "WM_types.h"
68
69 #include "DEG_depsgraph.h"
70 #include "DEG_depsgraph_query.h"
71
72 #include "ED_anim_api.h"
73 #include "ED_armature.h"
74 #include "ED_clip.h"
75 #include "ED_image.h"
76 #include "ED_keyframes_draw.h"
77 #include "ED_mesh.h"
78 #include "ED_object.h"
79 #include "ED_screen.h"
80 #include "ED_screen_types.h"
81 #include "ED_sequencer.h"
82 #include "ED_util.h"
83 #include "ED_undo.h"
84 #include "ED_view3d.h"
85
86 #include "RNA_access.h"
87 #include "RNA_define.h"
88 #include "RNA_enum_types.h"
89
90 #include "UI_interface.h"
91 #include "UI_resources.h"
92 #include "UI_view2d.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
105 bool ED_operator_regionactive(bContext *C)
106 {
107   if (CTX_wm_window(C) == NULL) {
108     return 0;
109   }
110   if (CTX_wm_screen(C) == NULL) {
111     return 0;
112   }
113   if (CTX_wm_region(C) == NULL) {
114     return 0;
115   }
116   return 1;
117 }
118
119 bool ED_operator_areaactive(bContext *C)
120 {
121   if (CTX_wm_window(C) == NULL) {
122     return 0;
123   }
124   if (CTX_wm_screen(C) == NULL) {
125     return 0;
126   }
127   if (CTX_wm_area(C) == NULL) {
128     return 0;
129   }
130   return 1;
131 }
132
133 bool ED_operator_screenactive(bContext *C)
134 {
135   if (CTX_wm_window(C) == NULL) {
136     return 0;
137   }
138   if (CTX_wm_screen(C) == NULL) {
139     return 0;
140   }
141   return 1;
142 }
143
144 /* XXX added this to prevent anim state to change during renders */
145 static bool ED_operator_screenactive_norender(bContext *C)
146 {
147   if (G.is_rendering) {
148     return 0;
149   }
150   if (CTX_wm_window(C) == NULL) {
151     return 0;
152   }
153   if (CTX_wm_screen(C) == NULL) {
154     return 0;
155   }
156   return 1;
157 }
158
159 /* when mouse is over area-edge */
160 bool ED_operator_screen_mainwinactive(bContext *C)
161 {
162   bScreen *screen;
163   if (CTX_wm_window(C) == NULL) {
164     return 0;
165   }
166   screen = CTX_wm_screen(C);
167   if (screen == NULL) {
168     return 0;
169   }
170   if (screen->active_region != NULL) {
171     return 0;
172   }
173   return 1;
174 }
175
176 bool ED_operator_scene(bContext *C)
177 {
178   Scene *scene = CTX_data_scene(C);
179   if (scene) {
180     return 1;
181   }
182   return 0;
183 }
184
185 bool ED_operator_scene_editable(bContext *C)
186 {
187   Scene *scene = CTX_data_scene(C);
188   if (scene && !ID_IS_LINKED(scene)) {
189     return 1;
190   }
191   return 0;
192 }
193
194 bool ED_operator_objectmode(bContext *C)
195 {
196   Scene *scene = CTX_data_scene(C);
197   Object *obact = CTX_data_active_object(C);
198
199   if (scene == NULL || ID_IS_LINKED(scene)) {
200     return 0;
201   }
202   if (CTX_data_edit_object(C)) {
203     return 0;
204   }
205
206   /* add a check for ob->mode too? */
207   if (obact && (obact->mode != OB_MODE_OBJECT)) {
208     return 0;
209   }
210
211   return 1;
212 }
213
214 static bool ed_spacetype_test(bContext *C, int type)
215 {
216   if (ED_operator_areaactive(C)) {
217     SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
218     return sl && (sl->spacetype == type);
219   }
220   return 0;
221 }
222
223 bool ED_operator_view3d_active(bContext *C)
224 {
225   return ed_spacetype_test(C, SPACE_VIEW3D);
226 }
227
228 bool ED_operator_region_view3d_active(bContext *C)
229 {
230   if (CTX_wm_region_view3d(C)) {
231     return true;
232   }
233
234   CTX_wm_operator_poll_msg_set(C, "expected a view3d region");
235   return false;
236 }
237
238 /* generic for any view2d which uses anim_ops */
239 bool ED_operator_animview_active(bContext *C)
240 {
241   if (ED_operator_areaactive(C)) {
242     SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
243     if (sl && (ELEM(sl->spacetype, SPACE_SEQ, SPACE_ACTION, SPACE_NLA, SPACE_GRAPH))) {
244       return true;
245     }
246   }
247
248   CTX_wm_operator_poll_msg_set(C, "expected a timeline/animation area to be active");
249   return 0;
250 }
251
252 bool ED_operator_outliner_active(bContext *C)
253 {
254   return ed_spacetype_test(C, SPACE_OUTLINER);
255 }
256
257 bool ED_operator_outliner_active_no_editobject(bContext *C)
258 {
259   if (ed_spacetype_test(C, SPACE_OUTLINER)) {
260     Object *ob = ED_object_active_context(C);
261     Object *obedit = CTX_data_edit_object(C);
262     if (ob && ob == obedit) {
263       return 0;
264     }
265     else {
266       return 1;
267     }
268   }
269   return 0;
270 }
271
272 bool ED_operator_file_active(bContext *C)
273 {
274   return ed_spacetype_test(C, SPACE_FILE);
275 }
276
277 bool ED_operator_action_active(bContext *C)
278 {
279   return ed_spacetype_test(C, SPACE_ACTION);
280 }
281
282 bool ED_operator_buttons_active(bContext *C)
283 {
284   return ed_spacetype_test(C, SPACE_PROPERTIES);
285 }
286
287 bool ED_operator_node_active(bContext *C)
288 {
289   SpaceNode *snode = CTX_wm_space_node(C);
290
291   if (snode && snode->edittree) {
292     return 1;
293   }
294
295   return 0;
296 }
297
298 bool ED_operator_node_editable(bContext *C)
299 {
300   SpaceNode *snode = CTX_wm_space_node(C);
301
302   if (snode && snode->edittree && !ID_IS_LINKED(snode->edittree)) {
303     return 1;
304   }
305
306   return 0;
307 }
308
309 bool ED_operator_graphedit_active(bContext *C)
310 {
311   return ed_spacetype_test(C, SPACE_GRAPH);
312 }
313
314 bool ED_operator_sequencer_active(bContext *C)
315 {
316   return ed_spacetype_test(C, SPACE_SEQ);
317 }
318
319 bool ED_operator_sequencer_active_editable(bContext *C)
320 {
321   return ed_spacetype_test(C, SPACE_SEQ) && ED_operator_scene_editable(C);
322 }
323
324 bool ED_operator_image_active(bContext *C)
325 {
326   return ed_spacetype_test(C, SPACE_IMAGE);
327 }
328
329 bool ED_operator_nla_active(bContext *C)
330 {
331   return ed_spacetype_test(C, SPACE_NLA);
332 }
333
334 bool ED_operator_info_active(bContext *C)
335 {
336   return ed_spacetype_test(C, SPACE_INFO);
337 }
338
339 bool ED_operator_console_active(bContext *C)
340 {
341   return ed_spacetype_test(C, SPACE_CONSOLE);
342 }
343
344 static bool ed_object_hidden(Object *ob)
345 {
346   /* if hidden but in edit mode, we still display, can happen with animation */
347   return ((ob->restrictflag & OB_RESTRICT_VIEWPORT) && !(ob->mode & OB_MODE_EDIT));
348 }
349
350 bool ED_operator_object_active(bContext *C)
351 {
352   Object *ob = ED_object_active_context(C);
353   return ((ob != NULL) && !ed_object_hidden(ob));
354 }
355
356 bool ED_operator_object_active_editable(bContext *C)
357 {
358   Object *ob = ED_object_active_context(C);
359   return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob));
360 }
361
362 bool ED_operator_object_active_editable_mesh(bContext *C)
363 {
364   Object *ob = ED_object_active_context(C);
365   return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob) && (ob->type == OB_MESH) &&
366           !ID_IS_LINKED(ob->data));
367 }
368
369 bool ED_operator_object_active_editable_font(bContext *C)
370 {
371   Object *ob = ED_object_active_context(C);
372   return ((ob != NULL) && !ID_IS_LINKED(ob) && !ed_object_hidden(ob) && (ob->type == OB_FONT));
373 }
374
375 bool ED_operator_editable_mesh(bContext *C)
376 {
377   Mesh *mesh = ED_mesh_context(C);
378   return (mesh != NULL) && !ID_IS_LINKED(mesh);
379 }
380
381 bool ED_operator_editmesh(bContext *C)
382 {
383   Object *obedit = CTX_data_edit_object(C);
384   if (obedit && obedit->type == OB_MESH) {
385     return NULL != BKE_editmesh_from_object(obedit);
386   }
387   return 0;
388 }
389
390 bool ED_operator_editmesh_view3d(bContext *C)
391 {
392   return ED_operator_editmesh(C) && ED_operator_view3d_active(C);
393 }
394
395 bool ED_operator_editmesh_region_view3d(bContext *C)
396 {
397   if (ED_operator_editmesh(C) && CTX_wm_region_view3d(C)) {
398     return 1;
399   }
400
401   CTX_wm_operator_poll_msg_set(C, "expected a view3d region & editmesh");
402   return 0;
403 }
404
405 bool ED_operator_editmesh_auto_smooth(bContext *C)
406 {
407   Object *obedit = CTX_data_edit_object(C);
408   if (obedit && obedit->type == OB_MESH && (((Mesh *)(obedit->data))->flag & ME_AUTOSMOOTH)) {
409     return NULL != BKE_editmesh_from_object(obedit);
410   }
411   return 0;
412 }
413
414 bool ED_operator_editarmature(bContext *C)
415 {
416   Object *obedit = CTX_data_edit_object(C);
417   if (obedit && obedit->type == OB_ARMATURE) {
418     return NULL != ((bArmature *)obedit->data)->edbo;
419   }
420   return 0;
421 }
422
423 /**
424  * \brief check for pose mode (no mixed modes)
425  *
426  * We want to enable most pose operations in weight paint mode,
427  * when it comes to transforming bones, but managing bones layers/groups
428  * can be left for pose mode only. (not weight paint mode)
429  */
430 bool ED_operator_posemode_exclusive(bContext *C)
431 {
432   Object *obact = CTX_data_active_object(C);
433
434   if (obact && !(obact->mode & OB_MODE_EDIT)) {
435     Object *obpose;
436     if ((obpose = BKE_object_pose_armature_get(obact))) {
437       if (obact == obpose) {
438         return 1;
439       }
440     }
441   }
442
443   return 0;
444 }
445
446 /* allows for pinned pose objects to be used in the object buttons
447  * and the non-active pose object to be used in the 3D view */
448 bool ED_operator_posemode_context(bContext *C)
449 {
450   Object *obpose = ED_pose_object_from_context(C);
451
452   if (obpose && !(obpose->mode & OB_MODE_EDIT)) {
453     if (BKE_object_pose_context_check(obpose)) {
454       return 1;
455     }
456   }
457
458   return 0;
459 }
460
461 bool ED_operator_posemode(bContext *C)
462 {
463   Object *obact = CTX_data_active_object(C);
464
465   if (obact && !(obact->mode & OB_MODE_EDIT)) {
466     Object *obpose;
467     if ((obpose = BKE_object_pose_armature_get(obact))) {
468       if ((obact == obpose) || (obact->mode & OB_MODE_WEIGHT_PAINT)) {
469         return 1;
470       }
471     }
472   }
473
474   return 0;
475 }
476
477 bool ED_operator_posemode_local(bContext *C)
478 {
479   if (ED_operator_posemode(C)) {
480     Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
481     bArmature *arm = ob->data;
482     return !(ID_IS_LINKED(&ob->id) || ID_IS_LINKED(&arm->id));
483   }
484   return false;
485 }
486
487 /* wrapper for ED_space_image_show_uvedit */
488 bool ED_operator_uvedit(bContext *C)
489 {
490   SpaceImage *sima = CTX_wm_space_image(C);
491   Object *obedit = CTX_data_edit_object(C);
492   return ED_space_image_show_uvedit(sima, obedit);
493 }
494
495 bool ED_operator_uvedit_space_image(bContext *C)
496 {
497   SpaceImage *sima = CTX_wm_space_image(C);
498   Object *obedit = CTX_data_edit_object(C);
499   return sima && ED_space_image_show_uvedit(sima, obedit);
500 }
501
502 bool ED_operator_uvmap(bContext *C)
503 {
504   Object *obedit = CTX_data_edit_object(C);
505   BMEditMesh *em = NULL;
506
507   if (obedit && obedit->type == OB_MESH) {
508     em = BKE_editmesh_from_object(obedit);
509   }
510
511   if (em && (em->bm->totface)) {
512     return true;
513   }
514
515   return false;
516 }
517
518 bool ED_operator_editsurfcurve(bContext *C)
519 {
520   Object *obedit = CTX_data_edit_object(C);
521   if (obedit && ELEM(obedit->type, OB_CURVE, OB_SURF)) {
522     return NULL != ((Curve *)obedit->data)->editnurb;
523   }
524   return 0;
525 }
526
527 bool ED_operator_editsurfcurve_region_view3d(bContext *C)
528 {
529   if (ED_operator_editsurfcurve(C) && CTX_wm_region_view3d(C)) {
530     return 1;
531   }
532
533   CTX_wm_operator_poll_msg_set(C, "expected a view3d region & editcurve");
534   return 0;
535 }
536
537 bool ED_operator_editcurve(bContext *C)
538 {
539   Object *obedit = CTX_data_edit_object(C);
540   if (obedit && obedit->type == OB_CURVE) {
541     return NULL != ((Curve *)obedit->data)->editnurb;
542   }
543   return 0;
544 }
545
546 bool ED_operator_editcurve_3d(bContext *C)
547 {
548   Object *obedit = CTX_data_edit_object(C);
549   if (obedit && obedit->type == OB_CURVE) {
550     Curve *cu = (Curve *)obedit->data;
551
552     return (cu->flag & CU_3D) && (NULL != cu->editnurb);
553   }
554   return 0;
555 }
556
557 bool ED_operator_editsurf(bContext *C)
558 {
559   Object *obedit = CTX_data_edit_object(C);
560   if (obedit && obedit->type == OB_SURF) {
561     return NULL != ((Curve *)obedit->data)->editnurb;
562   }
563   return 0;
564 }
565
566 bool ED_operator_editfont(bContext *C)
567 {
568   Object *obedit = CTX_data_edit_object(C);
569   if (obedit && obedit->type == OB_FONT) {
570     return NULL != ((Curve *)obedit->data)->editfont;
571   }
572   return 0;
573 }
574
575 bool ED_operator_editlattice(bContext *C)
576 {
577   Object *obedit = CTX_data_edit_object(C);
578   if (obedit && obedit->type == OB_LATTICE) {
579     return NULL != ((Lattice *)obedit->data)->editlatt;
580   }
581   return 0;
582 }
583
584 bool ED_operator_editmball(bContext *C)
585 {
586   Object *obedit = CTX_data_edit_object(C);
587   if (obedit && obedit->type == OB_MBALL) {
588     return NULL != ((MetaBall *)obedit->data)->editelems;
589   }
590   return 0;
591 }
592
593 bool ED_operator_mask(bContext *C)
594 {
595   ScrArea *sa = CTX_wm_area(C);
596   if (sa && sa->spacedata.first) {
597     switch (sa->spacetype) {
598       case SPACE_CLIP: {
599         SpaceClip *sc = sa->spacedata.first;
600         return ED_space_clip_check_show_maskedit(sc);
601       }
602       case SPACE_SEQ: {
603         SpaceSeq *sseq = sa->spacedata.first;
604         Scene *scene = CTX_data_scene(C);
605         return ED_space_sequencer_check_show_maskedit(sseq, scene);
606       }
607       case SPACE_IMAGE: {
608         SpaceImage *sima = sa->spacedata.first;
609         ViewLayer *view_layer = CTX_data_view_layer(C);
610         return ED_space_image_check_show_maskedit(sima, view_layer);
611       }
612     }
613   }
614
615   return false;
616 }
617
618 bool ED_operator_camera(bContext *C)
619 {
620   struct Camera *cam = CTX_data_pointer_get_type(C, "camera", &RNA_Camera).data;
621   return (cam != NULL);
622 }
623
624 /** \} */
625
626 /* -------------------------------------------------------------------- */
627 /** \name Internal Screen Utilities
628  * \{ */
629
630 static bool screen_active_editable(bContext *C)
631 {
632   if (ED_operator_screenactive(C)) {
633     /* no full window splitting allowed */
634     if (CTX_wm_screen(C)->state != SCREENNORMAL) {
635       return 0;
636     }
637     return 1;
638   }
639   return 0;
640 }
641
642 /** \} */
643
644 /* -------------------------------------------------------------------- */
645 /** \name Action Zone Operator
646  * \{ */
647
648 /* operator state vars used:
649  * none
650  *
651  * functions:
652  *
653  * apply() set action-zone event
654  *
655  * exit()   free customdata
656  *
657  * callbacks:
658  *
659  * exec()   never used
660  *
661  * invoke() check if in zone
662  * add customdata, put mouseco and area in it
663  * add modal handler
664  *
665  * modal()  accept modal events while doing it
666  * call apply() with gesture info, active window, nonactive window
667  * call exit() and remove handler when LMB confirm
668  */
669
670 typedef struct sActionzoneData {
671   ScrArea *sa1, *sa2;
672   AZone *az;
673   int x, y, gesture_dir, modifier;
674 } sActionzoneData;
675
676 /* quick poll to save operators to be created and handled */
677 static bool actionzone_area_poll(bContext *C)
678 {
679   wmWindow *win = CTX_wm_window(C);
680   bScreen *screen = WM_window_get_active_screen(win);
681
682   if (screen && win && win->eventstate) {
683     const int *xy = &win->eventstate->x;
684     AZone *az;
685
686     for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
687       for (az = sa->actionzones.first; az; az = az->next) {
688         if (BLI_rcti_isect_pt_v(&az->rect, xy)) {
689           return 1;
690         }
691       }
692     }
693   }
694   return 0;
695 }
696
697 /* the debug drawing of the click_rect is in area_draw_azone_fullscreen, keep both in sync */
698 static void fullscreen_click_rcti_init(
699     rcti *rect, const short x1, const short y1, const short x2, const short y2)
700 {
701   int x = x2 - ((float)x2 - x1) * 0.5f / UI_DPI_FAC;
702   int y = y2 - ((float)y2 - y1) * 0.5f / UI_DPI_FAC;
703   float icon_size = UI_DPI_ICON_SIZE + 7 * UI_DPI_FAC;
704
705   /* adjust the icon distance from the corner */
706   x += 36.0f / UI_DPI_FAC;
707   y += 36.0f / UI_DPI_FAC;
708
709   /* draws from the left bottom corner of the icon */
710   x -= UI_DPI_ICON_SIZE;
711   y -= UI_DPI_ICON_SIZE;
712
713   BLI_rcti_init(rect, x, x + icon_size, y, y + icon_size);
714 }
715
716 static bool azone_clipped_rect_calc(const AZone *az, rcti *r_rect_clip)
717 {
718   const ARegion *ar = az->ar;
719   *r_rect_clip = az->rect;
720   if (az->type == AZONE_REGION) {
721     if (ar->overlap && (ar->v2d.keeptot != V2D_KEEPTOT_STRICT) &&
722         /* Only when this isn't hidden (where it's displayed as an button that expands). */
723         ((az->ar->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) == 0)) {
724       /* A floating region to be resized, clip by the visible region. */
725       switch (az->edge) {
726         case AE_TOP_TO_BOTTOMRIGHT:
727         case AE_BOTTOM_TO_TOPLEFT: {
728           r_rect_clip->xmin = max_ii(
729               r_rect_clip->xmin,
730               (ar->winrct.xmin + UI_view2d_view_to_region_x(&ar->v2d, ar->v2d.tot.xmin)) -
731                   UI_REGION_OVERLAP_MARGIN);
732           r_rect_clip->xmax = min_ii(
733               r_rect_clip->xmax,
734               (ar->winrct.xmin + UI_view2d_view_to_region_x(&ar->v2d, ar->v2d.tot.xmax)) +
735                   UI_REGION_OVERLAP_MARGIN);
736           return true;
737         }
738         case AE_LEFT_TO_TOPRIGHT:
739         case AE_RIGHT_TO_TOPLEFT: {
740           r_rect_clip->ymin = max_ii(
741               r_rect_clip->ymin,
742               (ar->winrct.ymin + UI_view2d_view_to_region_y(&ar->v2d, ar->v2d.tot.ymin)) -
743                   UI_REGION_OVERLAP_MARGIN);
744           r_rect_clip->ymax = min_ii(
745               r_rect_clip->ymax,
746               (ar->winrct.ymin + UI_view2d_view_to_region_y(&ar->v2d, ar->v2d.tot.ymax)) +
747                   UI_REGION_OVERLAP_MARGIN);
748           return true;
749         }
750       }
751     }
752   }
753   return false;
754 }
755
756 static AZone *area_actionzone_refresh_xy(ScrArea *sa, const int xy[2], const bool test_only)
757 {
758   AZone *az = NULL;
759
760   for (az = sa->actionzones.first; az; az = az->next) {
761     rcti az_rect_clip;
762     if (BLI_rcti_isect_pt_v(&az->rect, xy) &&
763         /* Check clipping if this is clipped */
764         (!azone_clipped_rect_calc(az, &az_rect_clip) || BLI_rcti_isect_pt_v(&az_rect_clip, xy))) {
765
766       if (az->type == AZONE_AREA) {
767         break;
768       }
769       else if (az->type == AZONE_REGION) {
770         break;
771       }
772       else if (az->type == AZONE_FULLSCREEN) {
773         rcti click_rect;
774         fullscreen_click_rcti_init(&click_rect, az->x1, az->y1, az->x2, az->y2);
775         const bool click_isect = BLI_rcti_isect_pt_v(&click_rect, xy);
776
777         if (test_only) {
778           if (click_isect) {
779             break;
780           }
781         }
782         else {
783           if (click_isect) {
784             az->alpha = 1.0f;
785           }
786           else {
787             const int mouse_sq = SQUARE(xy[0] - az->x2) + SQUARE(xy[1] - az->y2);
788             const int spot_sq = SQUARE(AZONESPOTW);
789             const int fadein_sq = SQUARE(AZONEFADEIN);
790             const int fadeout_sq = SQUARE(AZONEFADEOUT);
791
792             if (mouse_sq < spot_sq) {
793               az->alpha = 1.0f;
794             }
795             else if (mouse_sq < fadein_sq) {
796               az->alpha = 1.0f;
797             }
798             else if (mouse_sq < fadeout_sq) {
799               az->alpha = 1.0f -
800                           ((float)(mouse_sq - fadein_sq)) / ((float)(fadeout_sq - fadein_sq));
801             }
802             else {
803               az->alpha = 0.0f;
804             }
805
806             /* fade in/out but no click */
807             az = NULL;
808           }
809
810           /* XXX force redraw to show/hide the action zone */
811           ED_area_tag_redraw(sa);
812           break;
813         }
814       }
815       else if (az->type == AZONE_REGION_SCROLL) {
816         ARegion *ar = az->ar;
817         View2D *v2d = &ar->v2d;
818         int scroll_flag = 0;
819         const int isect_value = UI_view2d_mouse_in_scrollers_ex(
820             ar, v2d, xy[0], xy[1], &scroll_flag);
821
822         /* Check if we even have scroll bars. */
823         if (((az->direction == AZ_SCROLL_HOR) && !(scroll_flag & V2D_SCROLL_HORIZONTAL)) ||
824             ((az->direction == AZ_SCROLL_VERT) && !(scroll_flag & V2D_SCROLL_VERTICAL))) {
825           /* no scrollbars, do nothing. */
826         }
827         else if (test_only) {
828           if (isect_value != 0) {
829             break;
830           }
831         }
832         else {
833           bool redraw = false;
834
835           if (isect_value == 'h') {
836             if (az->direction == AZ_SCROLL_HOR) {
837               az->alpha = 1.0f;
838               v2d->alpha_hor = 255;
839               redraw = true;
840             }
841           }
842           else if (isect_value == 'v') {
843             if (az->direction == AZ_SCROLL_VERT) {
844               az->alpha = 1.0f;
845               v2d->alpha_vert = 255;
846               redraw = true;
847             }
848           }
849           else {
850             const int local_xy[2] = {xy[0] - ar->winrct.xmin, xy[1] - ar->winrct.ymin};
851             float dist_fac = 0.0f, alpha = 0.0f;
852
853             if (az->direction == AZ_SCROLL_HOR) {
854               dist_fac = BLI_rcti_length_y(&v2d->hor, local_xy[1]) / AZONEFADEIN;
855               CLAMP(dist_fac, 0.0f, 1.0f);
856               alpha = 1.0f - dist_fac;
857
858               v2d->alpha_hor = alpha * 255;
859             }
860             else if (az->direction == AZ_SCROLL_VERT) {
861               dist_fac = BLI_rcti_length_x(&v2d->vert, local_xy[0]) / AZONEFADEIN;
862               CLAMP(dist_fac, 0.0f, 1.0f);
863               alpha = 1.0f - dist_fac;
864
865               v2d->alpha_vert = alpha * 255;
866             }
867             az->alpha = alpha;
868             redraw = true;
869           }
870
871           if (redraw) {
872             ED_region_tag_redraw_no_rebuild(ar);
873           }
874           /* Don't return! */
875         }
876       }
877     }
878     else if (!test_only && !IS_EQF(az->alpha, 0.0f)) {
879       if (az->type == AZONE_FULLSCREEN) {
880         az->alpha = 0.0f;
881         sa->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
882         ED_area_tag_redraw_no_rebuild(sa);
883       }
884       else if (az->type == AZONE_REGION_SCROLL) {
885         if (az->direction == AZ_SCROLL_VERT) {
886           az->alpha = az->ar->v2d.alpha_vert = 0;
887           sa->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
888           ED_region_tag_redraw_no_rebuild(az->ar);
889         }
890         else if (az->direction == AZ_SCROLL_HOR) {
891           az->alpha = az->ar->v2d.alpha_hor = 0;
892           sa->flag &= ~AREA_FLAG_ACTIONZONES_UPDATE;
893           ED_region_tag_redraw_no_rebuild(az->ar);
894         }
895         else {
896           BLI_assert(0);
897         }
898       }
899     }
900   }
901
902   return az;
903 }
904
905 /* Finds an action-zone by position in entire screen so azones can overlap. */
906 static AZone *screen_actionzone_find_xy(bScreen *sc, const int xy[2])
907 {
908   for (ScrArea *sa = sc->areabase.first; sa; sa = sa->next) {
909     AZone *az = area_actionzone_refresh_xy(sa, xy, true);
910     if (az != NULL) {
911       return az;
912     }
913   }
914   return NULL;
915 }
916
917 /* Returns the area that the azone belongs to */
918 static ScrArea *screen_actionzone_area(bScreen *sc, const AZone *az)
919 {
920   for (ScrArea *area = sc->areabase.first; area; area = area->next) {
921     for (AZone *zone = area->actionzones.first; zone; zone = zone->next) {
922       if (zone == az) {
923         return area;
924       }
925     }
926   }
927   return NULL;
928 }
929
930 AZone *ED_area_actionzone_find_xy(ScrArea *sa, const int xy[2])
931 {
932   return area_actionzone_refresh_xy(sa, xy, true);
933 }
934
935 AZone *ED_area_azones_update(ScrArea *sa, const int xy[2])
936 {
937   return area_actionzone_refresh_xy(sa, xy, false);
938 }
939
940 static void actionzone_exit(wmOperator *op)
941 {
942   if (op->customdata) {
943     MEM_freeN(op->customdata);
944   }
945   op->customdata = NULL;
946 }
947
948 /* send EVT_ACTIONZONE event */
949 static void actionzone_apply(bContext *C, wmOperator *op, int type)
950 {
951   wmEvent event;
952   wmWindow *win = CTX_wm_window(C);
953   sActionzoneData *sad = op->customdata;
954
955   sad->modifier = RNA_int_get(op->ptr, "modifier");
956
957   wm_event_init_from_window(win, &event);
958
959   if (type == AZONE_AREA) {
960     event.type = EVT_ACTIONZONE_AREA;
961   }
962   else if (type == AZONE_FULLSCREEN) {
963     event.type = EVT_ACTIONZONE_FULLSCREEN;
964   }
965   else {
966     event.type = EVT_ACTIONZONE_REGION;
967   }
968
969   event.val = KM_NOTHING;
970   event.customdata = op->customdata;
971   event.customdatafree = true;
972   op->customdata = NULL;
973
974   wm_event_add(win, &event);
975 }
976
977 static int actionzone_invoke(bContext *C, wmOperator *op, const wmEvent *event)
978 {
979   bScreen *sc = CTX_wm_screen(C);
980   AZone *az = screen_actionzone_find_xy(sc, &event->x);
981   sActionzoneData *sad;
982
983   /* Quick escape - Scroll azones only hide/unhide the scroll-bars,
984    * they have their own handling. */
985   if (az == NULL || ELEM(az->type, AZONE_REGION_SCROLL)) {
986     return OPERATOR_PASS_THROUGH;
987   }
988
989   /* ok we do the action-zone */
990   sad = op->customdata = MEM_callocN(sizeof(sActionzoneData), "sActionzoneData");
991   sad->sa1 = screen_actionzone_area(sc, az);
992   sad->az = az;
993   sad->x = event->x;
994   sad->y = event->y;
995
996   /* region azone directly reacts on mouse clicks */
997   if (ELEM(sad->az->type, AZONE_REGION, AZONE_FULLSCREEN)) {
998     actionzone_apply(C, op, sad->az->type);
999     actionzone_exit(op);
1000     return OPERATOR_FINISHED;
1001   }
1002   else {
1003     /* add modal handler */
1004     WM_event_add_modal_handler(C, op);
1005
1006     return OPERATOR_RUNNING_MODAL;
1007   }
1008 }
1009
1010 static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event)
1011 {
1012   bScreen *sc = CTX_wm_screen(C);
1013   sActionzoneData *sad = op->customdata;
1014
1015   switch (event->type) {
1016     case MOUSEMOVE: {
1017       bool is_gesture;
1018
1019       const int delta_x = (event->x - sad->x);
1020       const int delta_y = (event->y - sad->y);
1021
1022       /* Movement in dominant direction. */
1023       const int delta_max = max_ii(ABS(delta_x), ABS(delta_y));
1024
1025       /* Movement in dominant direction before action taken. */
1026       const int join_threshold = (0.6 * U.widget_unit);
1027       const int split_threshold = (1.2 * U.widget_unit);
1028       const int area_threshold = (0.1 * U.widget_unit);
1029
1030       /* Calculate gesture cardinal direction. */
1031       if (delta_y > ABS(delta_x)) {
1032         sad->gesture_dir = 'n';
1033       }
1034       else if (delta_x >= ABS(delta_y)) {
1035         sad->gesture_dir = 'e';
1036       }
1037       else if (delta_y < -ABS(delta_x)) {
1038         sad->gesture_dir = 's';
1039       }
1040       else {
1041         sad->gesture_dir = 'w';
1042       }
1043
1044       if (sad->az->type == AZONE_AREA) {
1045         wmWindow *win = CTX_wm_window(C);
1046         rcti screen_rect;
1047
1048         WM_window_screen_rect_calc(win, &screen_rect);
1049
1050         /* Have we dragged off the zone and are not on an edge? */
1051         if ((ED_area_actionzone_find_xy(sad->sa1, &event->x) != sad->az) &&
1052             (screen_geom_area_map_find_active_scredge(
1053                  AREAMAP_FROM_SCREEN(sc), &screen_rect, event->x, event->y) == NULL)) {
1054           /* Are we still in same area? */
1055           if (BKE_screen_find_area_xy(sc, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) {
1056             /* Same area, so possible split. */
1057             WM_cursor_set(
1058                 win, (ELEM(sad->gesture_dir, 'n', 's')) ? BC_V_SPLITCURSOR : BC_H_SPLITCURSOR);
1059             is_gesture = (delta_max > split_threshold);
1060           }
1061           else {
1062             /* Different area, so posible join. */
1063             if (sad->gesture_dir == 'n') {
1064               WM_cursor_set(win, BC_N_ARROWCURSOR);
1065             }
1066             else if (sad->gesture_dir == 's') {
1067               WM_cursor_set(win, BC_S_ARROWCURSOR);
1068             }
1069             else if (sad->gesture_dir == 'e') {
1070               WM_cursor_set(win, BC_E_ARROWCURSOR);
1071             }
1072             else {
1073               WM_cursor_set(win, BC_W_ARROWCURSOR);
1074             }
1075             is_gesture = (delta_max > join_threshold);
1076           }
1077         }
1078         else {
1079           WM_cursor_set(CTX_wm_window(C), BC_CROSSCURSOR);
1080           is_gesture = false;
1081         }
1082       }
1083       else {
1084         is_gesture = (delta_max > area_threshold);
1085       }
1086
1087       /* gesture is large enough? */
1088       if (is_gesture) {
1089         /* second area, for join when (sa1 != sa2) */
1090         sad->sa2 = BKE_screen_find_area_xy(sc, SPACE_TYPE_ANY, event->x, event->y);
1091         /* apply sends event */
1092         actionzone_apply(C, op, sad->az->type);
1093         actionzone_exit(op);
1094
1095         return OPERATOR_FINISHED;
1096       }
1097       break;
1098     }
1099     case ESCKEY:
1100       actionzone_exit(op);
1101       return OPERATOR_CANCELLED;
1102     case LEFTMOUSE:
1103       actionzone_exit(op);
1104       return OPERATOR_CANCELLED;
1105   }
1106
1107   return OPERATOR_RUNNING_MODAL;
1108 }
1109
1110 static void actionzone_cancel(bContext *UNUSED(C), wmOperator *op)
1111 {
1112   actionzone_exit(op);
1113 }
1114
1115 static void SCREEN_OT_actionzone(wmOperatorType *ot)
1116 {
1117   /* identifiers */
1118   ot->name = "Handle Area Action Zones";
1119   ot->description = "Handle area action zones for mouse actions/gestures";
1120   ot->idname = "SCREEN_OT_actionzone";
1121
1122   ot->invoke = actionzone_invoke;
1123   ot->modal = actionzone_modal;
1124   ot->poll = actionzone_area_poll;
1125   ot->cancel = actionzone_cancel;
1126
1127   /* flags */
1128   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
1129
1130   RNA_def_int(ot->srna, "modifier", 0, 0, 2, "Modifier", "Modifier state", 0, 2);
1131 }
1132
1133 /** \} */
1134
1135 /* -------------------------------------------------------------------- */
1136 /** \name Swap Area Operator
1137  * \{ */
1138
1139 /* operator state vars used:
1140  * sa1      start area
1141  * sa2      area to swap with
1142  *
1143  * functions:
1144  *
1145  * init()   set custom data for operator, based on action-zone event custom data
1146  *
1147  * cancel() cancel the operator
1148  *
1149  * exit()   cleanup, send notifier
1150  *
1151  * callbacks:
1152  *
1153  * invoke() gets called on shift+lmb drag in action-zone
1154  * call init(), add handler
1155  *
1156  * modal()  accept modal events while doing it
1157  */
1158
1159 typedef struct sAreaSwapData {
1160   ScrArea *sa1, *sa2;
1161 } sAreaSwapData;
1162
1163 static int area_swap_init(wmOperator *op, const wmEvent *event)
1164 {
1165   sAreaSwapData *sd = NULL;
1166   sActionzoneData *sad = event->customdata;
1167
1168   if (sad == NULL || sad->sa1 == NULL) {
1169     return 0;
1170   }
1171
1172   sd = MEM_callocN(sizeof(sAreaSwapData), "sAreaSwapData");
1173   sd->sa1 = sad->sa1;
1174   sd->sa2 = sad->sa2;
1175   op->customdata = sd;
1176
1177   return 1;
1178 }
1179
1180 static void area_swap_exit(bContext *C, wmOperator *op)
1181 {
1182   WM_cursor_modal_restore(CTX_wm_window(C));
1183   if (op->customdata) {
1184     MEM_freeN(op->customdata);
1185   }
1186   op->customdata = NULL;
1187 }
1188
1189 static void area_swap_cancel(bContext *C, wmOperator *op)
1190 {
1191   area_swap_exit(C, op);
1192 }
1193
1194 static int area_swap_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1195 {
1196
1197   if (!area_swap_init(op, event)) {
1198     return OPERATOR_PASS_THROUGH;
1199   }
1200
1201   /* add modal handler */
1202   WM_cursor_modal_set(CTX_wm_window(C), BC_SWAPAREA_CURSOR);
1203   WM_event_add_modal_handler(C, op);
1204
1205   return OPERATOR_RUNNING_MODAL;
1206 }
1207
1208 static int area_swap_modal(bContext *C, wmOperator *op, const wmEvent *event)
1209 {
1210   sActionzoneData *sad = op->customdata;
1211
1212   switch (event->type) {
1213     case MOUSEMOVE:
1214       /* second area, for join */
1215       sad->sa2 = BKE_screen_find_area_xy(CTX_wm_screen(C), SPACE_TYPE_ANY, event->x, event->y);
1216       break;
1217     case LEFTMOUSE: /* release LMB */
1218       if (event->val == KM_RELEASE) {
1219         if (!sad->sa2 || sad->sa1 == sad->sa2) {
1220           area_swap_cancel(C, op);
1221           return OPERATOR_CANCELLED;
1222         }
1223
1224         ED_area_tag_redraw(sad->sa1);
1225         ED_area_tag_redraw(sad->sa2);
1226
1227         ED_area_swapspace(C, sad->sa1, sad->sa2);
1228
1229         area_swap_exit(C, op);
1230
1231         WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1232
1233         return OPERATOR_FINISHED;
1234       }
1235       break;
1236
1237     case ESCKEY:
1238       area_swap_cancel(C, op);
1239       return OPERATOR_CANCELLED;
1240   }
1241   return OPERATOR_RUNNING_MODAL;
1242 }
1243
1244 static void SCREEN_OT_area_swap(wmOperatorType *ot)
1245 {
1246   ot->name = "Swap Areas";
1247   ot->description = "Swap selected areas screen positions";
1248   ot->idname = "SCREEN_OT_area_swap";
1249
1250   ot->invoke = area_swap_invoke;
1251   ot->modal = area_swap_modal;
1252   ot->poll = ED_operator_areaactive;
1253   ot->cancel = area_swap_cancel;
1254
1255   ot->flag = OPTYPE_BLOCKING;
1256 }
1257
1258 /** \} */
1259
1260 /* -------------------------------------------------------------------- */
1261 /** \name Area Duplicate Operator
1262  *
1263  * Create new window from area.
1264  * \{ */
1265
1266 /* operator callback */
1267 static int area_dupli_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1268 {
1269   Main *bmain = CTX_data_main(C);
1270   wmWindow *newwin, *win = CTX_wm_window(C);
1271   Scene *scene;
1272   WorkSpace *workspace = WM_window_get_active_workspace(win);
1273   WorkSpaceLayout *layout_old = WM_window_get_active_layout(win);
1274   WorkSpaceLayout *layout_new;
1275   bScreen *newsc;
1276   ScrArea *sa;
1277   rcti rect;
1278
1279   win = CTX_wm_window(C);
1280   scene = CTX_data_scene(C);
1281   sa = CTX_wm_area(C);
1282
1283   /* XXX hrmf! */
1284   if (event->type == EVT_ACTIONZONE_AREA) {
1285     sActionzoneData *sad = event->customdata;
1286
1287     if (sad == NULL) {
1288       return OPERATOR_PASS_THROUGH;
1289     }
1290
1291     sa = sad->sa1;
1292   }
1293
1294   /* adds window to WM */
1295   rect = sa->totrct;
1296   BLI_rcti_translate(&rect, win->posx, win->posy);
1297   rect.xmax = rect.xmin + BLI_rcti_size_x(&rect) / U.pixelsize;
1298   rect.ymax = rect.ymin + BLI_rcti_size_y(&rect) / U.pixelsize;
1299
1300   newwin = WM_window_open(C, &rect);
1301   if (newwin == NULL) {
1302     BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
1303     goto finally;
1304   }
1305
1306   *newwin->stereo3d_format = *win->stereo3d_format;
1307
1308   newwin->scene = scene;
1309
1310   BKE_workspace_active_set(newwin->workspace_hook, workspace);
1311   /* allocs new screen and adds to newly created window, using window size */
1312   layout_new = ED_workspace_layout_add(
1313       bmain, workspace, newwin, BKE_workspace_layout_name_get(layout_old));
1314   newsc = BKE_workspace_layout_screen_get(layout_new);
1315   WM_window_set_active_layout(newwin, workspace, layout_new);
1316
1317   /* copy area to new screen */
1318   ED_area_data_copy((ScrArea *)newsc->areabase.first, sa, true);
1319
1320   ED_area_tag_redraw((ScrArea *)newsc->areabase.first);
1321
1322   /* screen, areas init */
1323   WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
1324
1325 finally:
1326   if (event->type == EVT_ACTIONZONE_AREA) {
1327     actionzone_exit(op);
1328   }
1329
1330   if (newwin) {
1331     return OPERATOR_FINISHED;
1332   }
1333   else {
1334     return OPERATOR_CANCELLED;
1335   }
1336 }
1337
1338 static void SCREEN_OT_area_dupli(wmOperatorType *ot)
1339 {
1340   ot->name = "Duplicate Area into New Window";
1341   ot->description = "Duplicate selected area into new window";
1342   ot->idname = "SCREEN_OT_area_dupli";
1343
1344   ot->invoke = area_dupli_invoke;
1345   ot->poll = ED_operator_areaactive;
1346 }
1347
1348 /** \} */
1349
1350 /* -------------------------------------------------------------------- */
1351 /** \name Move Area Edge Operator
1352  * \{ */
1353
1354 /* operator state vars used:
1355  * x, y             mouse coord near edge
1356  * delta            movement of edge
1357  *
1358  * functions:
1359  *
1360  * init()   set default property values, find edge based on mouse coords, test
1361  * if the edge can be moved, select edges, calculate min and max movement
1362  *
1363  * apply()  apply delta on selection
1364  *
1365  * exit()   cleanup, send notifier
1366  *
1367  * cancel() cancel moving
1368  *
1369  * callbacks:
1370  *
1371  * exec()   execute without any user interaction, based on properties
1372  * call init(), apply(), exit()
1373  *
1374  * invoke() gets called on mouse click near edge
1375  * call init(), add handler
1376  *
1377  * modal()  accept modal events while doing it
1378  * call apply() with delta motion
1379  * call exit() and remove handler
1380  */
1381
1382 typedef struct sAreaMoveData {
1383   int bigger, smaller, origval, step;
1384   char dir;
1385   enum AreaMoveSnapType {
1386     /* Snapping disabled */
1387     SNAP_NONE = 0,
1388     /* Snap to an invisible grid with a unit defined in AREAGRID */
1389     SNAP_AREAGRID,
1390     /* Snap to fraction (half, third.. etc) and adjacent edges. */
1391     SNAP_FRACTION_AND_ADJACENT,
1392     /* Snap to either bigger or smaller, nothing in-between (used for
1393      * global areas). This has priority over other snap types, if it is
1394      * used, toggling SNAP_FRACTION_AND_ADJACENT doesn't work. */
1395     SNAP_BIGGER_SMALLER_ONLY,
1396   } snap_type;
1397 } sAreaMoveData;
1398
1399 /* helper call to move area-edge, sets limits
1400  * need window bounds in order to get correct limits */
1401 static void area_move_set_limits(
1402     wmWindow *win, bScreen *sc, int dir, int *bigger, int *smaller, bool *use_bigger_smaller_snap)
1403 {
1404   rcti window_rect;
1405   int areaminy = ED_area_headersize();
1406   int areamin;
1407
1408   /* we check all areas and test for free space with MINSIZE */
1409   *bigger = *smaller = 100000;
1410
1411   if (use_bigger_smaller_snap != NULL) {
1412     *use_bigger_smaller_snap = false;
1413     for (ScrArea *area = win->global_areas.areabase.first; area; area = area->next) {
1414       int size_min = ED_area_global_min_size_y(area) - 1;
1415       int size_max = ED_area_global_max_size_y(area) - 1;
1416
1417       size_min = max_ii(size_min, 0);
1418       BLI_assert(size_min <= size_max);
1419
1420       /* logic here is only tested for lower edge :) */
1421       /* left edge */
1422       if ((area->v1->editflag && area->v2->editflag)) {
1423         *smaller = area->v4->vec.x - size_max;
1424         *bigger = area->v4->vec.x - size_min;
1425         *use_bigger_smaller_snap = true;
1426         return;
1427       }
1428       /* top edge */
1429       else if ((area->v2->editflag && area->v3->editflag)) {
1430         *smaller = area->v1->vec.y + size_min;
1431         *bigger = area->v1->vec.y + size_max;
1432         *use_bigger_smaller_snap = true;
1433         return;
1434       }
1435       /* right edge */
1436       else if ((area->v3->editflag && area->v4->editflag)) {
1437         *smaller = area->v1->vec.x + size_min;
1438         *bigger = area->v1->vec.x + size_max;
1439         *use_bigger_smaller_snap = true;
1440         return;
1441       }
1442       /* lower edge */
1443       else if ((area->v4->editflag && area->v1->editflag)) {
1444         *smaller = area->v2->vec.y - size_max;
1445         *bigger = area->v2->vec.y - size_min;
1446         *use_bigger_smaller_snap = true;
1447         return;
1448       }
1449     }
1450   }
1451
1452   WM_window_rect_calc(win, &window_rect);
1453
1454   for (ScrArea *sa = sc->areabase.first; sa; sa = sa->next) {
1455     if (dir == 'h') {
1456       int y1;
1457       areamin = areaminy;
1458
1459       if (sa->v1->vec.y > window_rect.ymin) {
1460         areamin += U.pixelsize;
1461       }
1462       if (sa->v2->vec.y < (window_rect.ymax - 1)) {
1463         areamin += U.pixelsize;
1464       }
1465
1466       y1 = screen_geom_area_height(sa) - areamin;
1467
1468       /* if top or down edge selected, test height */
1469       if (sa->v1->editflag && sa->v4->editflag) {
1470         *bigger = min_ii(*bigger, y1);
1471       }
1472       else if (sa->v2->editflag && sa->v3->editflag) {
1473         *smaller = min_ii(*smaller, y1);
1474       }
1475     }
1476     else {
1477       int x1;
1478       areamin = AREAMINX;
1479
1480       if (sa->v1->vec.x > window_rect.xmin) {
1481         areamin += U.pixelsize;
1482       }
1483       if (sa->v4->vec.x < (window_rect.xmax - 1)) {
1484         areamin += U.pixelsize;
1485       }
1486
1487       x1 = screen_geom_area_width(sa) - areamin;
1488
1489       /* if left or right edge selected, test width */
1490       if (sa->v1->editflag && sa->v2->editflag) {
1491         *bigger = min_ii(*bigger, x1);
1492       }
1493       else if (sa->v3->editflag && sa->v4->editflag) {
1494         *smaller = min_ii(*smaller, x1);
1495       }
1496     }
1497   }
1498 }
1499
1500 /* validate selection inside screen, set variables OK */
1501 /* return 0: init failed */
1502 static int area_move_init(bContext *C, wmOperator *op)
1503 {
1504   bScreen *sc = CTX_wm_screen(C);
1505   wmWindow *win = CTX_wm_window(C);
1506   ScrEdge *actedge;
1507   sAreaMoveData *md;
1508   int x, y;
1509
1510   /* required properties */
1511   x = RNA_int_get(op->ptr, "x");
1512   y = RNA_int_get(op->ptr, "y");
1513
1514   /* setup */
1515   actedge = screen_geom_find_active_scredge(win, sc, x, y);
1516   if (actedge == NULL) {
1517     return 0;
1518   }
1519
1520   md = MEM_callocN(sizeof(sAreaMoveData), "sAreaMoveData");
1521   op->customdata = md;
1522
1523   md->dir = screen_geom_edge_is_horizontal(actedge) ? 'h' : 'v';
1524   if (md->dir == 'h') {
1525     md->origval = actedge->v1->vec.y;
1526   }
1527   else {
1528     md->origval = actedge->v1->vec.x;
1529   }
1530
1531   screen_geom_select_connected_edge(win, actedge);
1532   /* now all vertices with 'flag == 1' are the ones that can be moved. Move this to editflag */
1533   ED_screen_verts_iter(win, sc, v1)
1534   {
1535     v1->editflag = v1->flag;
1536   }
1537
1538   bool use_bigger_smaller_snap = false;
1539   area_move_set_limits(win, sc, md->dir, &md->bigger, &md->smaller, &use_bigger_smaller_snap);
1540
1541   md->snap_type = use_bigger_smaller_snap ? SNAP_BIGGER_SMALLER_ONLY : SNAP_AREAGRID;
1542
1543   return 1;
1544 }
1545
1546 static int area_snap_calc_location(const bScreen *sc,
1547                                    const enum AreaMoveSnapType snap_type,
1548                                    const int delta,
1549                                    const int origval,
1550                                    const int dir,
1551                                    const int bigger,
1552                                    const int smaller)
1553 {
1554   BLI_assert(snap_type != SNAP_NONE);
1555   int m_cursor_final = -1;
1556   const int m_cursor = origval + delta;
1557   const int m_span = (float)(bigger + smaller);
1558   const int m_min = origval - smaller;
1559   // const int axis_max = axis_min + m_span;
1560
1561   switch (snap_type) {
1562     case SNAP_AREAGRID:
1563       m_cursor_final = m_cursor;
1564       if (delta != bigger && delta != -smaller) {
1565         m_cursor_final -= (m_cursor % AREAGRID);
1566         CLAMP(m_cursor_final, origval - smaller, origval + bigger);
1567       }
1568       break;
1569
1570     case SNAP_BIGGER_SMALLER_ONLY:
1571       m_cursor_final = (m_cursor >= bigger) ? bigger : smaller;
1572       break;
1573
1574     case SNAP_FRACTION_AND_ADJACENT: {
1575       const int axis = (dir == 'v') ? 0 : 1;
1576       int snap_dist_best = INT_MAX;
1577       {
1578         const float div_array[] = {
1579             /* Middle. */
1580             1.0f / 2.0f,
1581             /* Thirds. */
1582             1.0f / 3.0f,
1583             2.0f / 3.0f,
1584             /* Quaters. */
1585             1.0f / 4.0f,
1586             3.0f / 4.0f,
1587             /* Eighth. */
1588             1.0f / 8.0f,
1589             3.0f / 8.0f,
1590             5.0f / 8.0f,
1591             7.0f / 8.0f,
1592         };
1593         /* Test the snap to the best division. */
1594         for (int i = 0; i < ARRAY_SIZE(div_array); i++) {
1595           const int m_cursor_test = m_min + round_fl_to_int(m_span * div_array[i]);
1596           const int snap_dist_test = abs(m_cursor - m_cursor_test);
1597           if (snap_dist_best >= snap_dist_test) {
1598             snap_dist_best = snap_dist_test;
1599             m_cursor_final = m_cursor_test;
1600           }
1601         }
1602       }
1603
1604       for (const ScrVert *v1 = sc->vertbase.first; v1; v1 = v1->next) {
1605         if (!v1->editflag) {
1606           continue;
1607         }
1608         const int v_loc = (&v1->vec.x)[!axis];
1609
1610         for (const ScrVert *v2 = sc->vertbase.first; v2; v2 = v2->next) {
1611           if (v2->editflag) {
1612             continue;
1613           }
1614           if (v_loc == (&v2->vec.x)[!axis]) {
1615             const int v_loc2 = (&v2->vec.x)[axis];
1616             /* Do not snap to the vertices at the ends. */
1617             if ((origval - smaller) < v_loc2 && v_loc2 < (origval + bigger)) {
1618               const int snap_dist_test = abs(m_cursor - v_loc2);
1619               if (snap_dist_best >= snap_dist_test) {
1620                 snap_dist_best = snap_dist_test;
1621                 m_cursor_final = v_loc2;
1622               }
1623             }
1624           }
1625         }
1626       }
1627       break;
1628     }
1629     case SNAP_NONE:
1630       break;
1631   }
1632
1633   BLI_assert(ELEM(snap_type, SNAP_BIGGER_SMALLER_ONLY) ||
1634              IN_RANGE_INCL(m_cursor_final, origval - smaller, origval + bigger));
1635
1636   return m_cursor_final;
1637 }
1638
1639 /* moves selected screen edge amount of delta, used by split & move */
1640 static void area_move_apply_do(const bContext *C,
1641                                int delta,
1642                                const int origval,
1643                                const int dir,
1644                                const int bigger,
1645                                const int smaller,
1646                                const enum AreaMoveSnapType snap_type)
1647 {
1648   wmWindow *win = CTX_wm_window(C);
1649   bScreen *sc = CTX_wm_screen(C);
1650   short final_loc = -1;
1651   bool doredraw = false;
1652
1653   if (snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1654     CLAMP(delta, -smaller, bigger);
1655   }
1656
1657   if (snap_type == SNAP_NONE) {
1658     final_loc = origval + delta;
1659   }
1660   else {
1661     final_loc = area_snap_calc_location(sc, snap_type, delta, origval, dir, bigger, smaller);
1662   }
1663
1664   BLI_assert(final_loc != -1);
1665   short axis = (dir == 'v') ? 0 : 1;
1666
1667   ED_screen_verts_iter(win, sc, v1)
1668   {
1669     if (v1->editflag) {
1670       short oldval = (&v1->vec.x)[axis];
1671       (&v1->vec.x)[axis] = final_loc;
1672
1673       if (oldval == final_loc) {
1674         /* nothing will change to the other vertices either. */
1675         break;
1676       }
1677       doredraw = true;
1678     }
1679   }
1680
1681   /* only redraw if we actually moved a screen vert, for AREAGRID */
1682   if (doredraw) {
1683     bool redraw_all = false;
1684     ED_screen_areas_iter(win, sc, sa)
1685     {
1686       if (sa->v1->editflag || sa->v2->editflag || sa->v3->editflag || sa->v4->editflag) {
1687         if (ED_area_is_global(sa)) {
1688           /* Snap to minimum or maximum for global areas. */
1689           int height = round_fl_to_int(screen_geom_area_height(sa) / UI_DPI_FAC);
1690           if (abs(height - sa->global->size_min) < abs(height - sa->global->size_max)) {
1691             sa->global->cur_fixed_height = sa->global->size_min;
1692           }
1693           else {
1694             sa->global->cur_fixed_height = sa->global->size_max;
1695           }
1696
1697           sc->do_refresh = true;
1698           redraw_all = true;
1699         }
1700         ED_area_tag_redraw(sa);
1701       }
1702     }
1703     if (redraw_all) {
1704       ED_screen_areas_iter(win, sc, sa)
1705       {
1706         ED_area_tag_redraw(sa);
1707       }
1708     }
1709
1710     ED_screen_global_areas_sync(win);
1711
1712     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); /* redraw everything */
1713     /* Update preview thumbnail */
1714     BKE_icon_changed(sc->id.icon_id);
1715   }
1716 }
1717
1718 static void area_move_apply(bContext *C, wmOperator *op)
1719 {
1720   sAreaMoveData *md = op->customdata;
1721   int delta = RNA_int_get(op->ptr, "delta");
1722
1723   area_move_apply_do(C, delta, md->origval, md->dir, md->bigger, md->smaller, md->snap_type);
1724 }
1725
1726 static void area_move_exit(bContext *C, wmOperator *op)
1727 {
1728   if (op->customdata) {
1729     MEM_freeN(op->customdata);
1730   }
1731   op->customdata = NULL;
1732
1733   /* this makes sure aligned edges will result in aligned grabbing */
1734   BKE_screen_remove_double_scrverts(CTX_wm_screen(C));
1735   BKE_screen_remove_double_scredges(CTX_wm_screen(C));
1736
1737   G.moving &= ~G_TRANSFORM_WM;
1738 }
1739
1740 static int area_move_exec(bContext *C, wmOperator *op)
1741 {
1742   if (!area_move_init(C, op)) {
1743     return OPERATOR_CANCELLED;
1744   }
1745
1746   area_move_apply(C, op);
1747   area_move_exit(C, op);
1748
1749   return OPERATOR_FINISHED;
1750 }
1751
1752 /* interaction callback */
1753 static int area_move_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1754 {
1755   RNA_int_set(op->ptr, "x", event->x);
1756   RNA_int_set(op->ptr, "y", event->y);
1757
1758   if (!area_move_init(C, op)) {
1759     return OPERATOR_PASS_THROUGH;
1760   }
1761
1762   G.moving |= G_TRANSFORM_WM;
1763
1764   /* add temp handler */
1765   WM_event_add_modal_handler(C, op);
1766
1767   return OPERATOR_RUNNING_MODAL;
1768 }
1769
1770 static void area_move_cancel(bContext *C, wmOperator *op)
1771 {
1772
1773   RNA_int_set(op->ptr, "delta", 0);
1774   area_move_apply(C, op);
1775   area_move_exit(C, op);
1776 }
1777
1778 /* modal callback for while moving edges */
1779 static int area_move_modal(bContext *C, wmOperator *op, const wmEvent *event)
1780 {
1781   sAreaMoveData *md = op->customdata;
1782   int delta, x, y;
1783
1784   /* execute the events */
1785   switch (event->type) {
1786     case MOUSEMOVE: {
1787       x = RNA_int_get(op->ptr, "x");
1788       y = RNA_int_get(op->ptr, "y");
1789
1790       delta = (md->dir == 'v') ? event->x - x : event->y - y;
1791       RNA_int_set(op->ptr, "delta", delta);
1792
1793       area_move_apply(C, op);
1794       break;
1795     }
1796     case EVT_MODAL_MAP: {
1797       switch (event->val) {
1798         case KM_MODAL_APPLY:
1799           area_move_exit(C, op);
1800           return OPERATOR_FINISHED;
1801
1802         case KM_MODAL_CANCEL:
1803           area_move_cancel(C, op);
1804           return OPERATOR_CANCELLED;
1805
1806         case KM_MODAL_SNAP_ON:
1807           if (md->snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1808             md->snap_type = SNAP_FRACTION_AND_ADJACENT;
1809           }
1810           break;
1811
1812         case KM_MODAL_SNAP_OFF:
1813           if (md->snap_type != SNAP_BIGGER_SMALLER_ONLY) {
1814             md->snap_type = SNAP_AREAGRID;
1815           }
1816           break;
1817       }
1818       break;
1819     }
1820   }
1821
1822   return OPERATOR_RUNNING_MODAL;
1823 }
1824
1825 static void SCREEN_OT_area_move(wmOperatorType *ot)
1826 {
1827   /* identifiers */
1828   ot->name = "Move Area Edges";
1829   ot->description = "Move selected area edges";
1830   ot->idname = "SCREEN_OT_area_move";
1831
1832   ot->exec = area_move_exec;
1833   ot->invoke = area_move_invoke;
1834   ot->cancel = area_move_cancel;
1835   ot->modal = area_move_modal;
1836   ot->poll = ED_operator_screen_mainwinactive; /* when mouse is over area-edge */
1837
1838   /* flags */
1839   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
1840
1841   /* rna */
1842   RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
1843   RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
1844   RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
1845 }
1846
1847 /** \} */
1848
1849 /* -------------------------------------------------------------------- */
1850 /** \name Split Area Operator
1851  * \{ */
1852
1853 /*
1854  * operator state vars:
1855  * fac              spit point
1856  * dir              direction 'v' or 'h'
1857  *
1858  * operator customdata:
1859  * area             pointer to (active) area
1860  * x, y             last used mouse pos
1861  * (more, see below)
1862  *
1863  * functions:
1864  *
1865  * init()   set default property values, find area based on context
1866  *
1867  * apply()  split area based on state vars
1868  *
1869  * exit()   cleanup, send notifier
1870  *
1871  * cancel() remove duplicated area
1872  *
1873  * callbacks:
1874  *
1875  * exec()   execute without any user interaction, based on state vars
1876  * call init(), apply(), exit()
1877  *
1878  * invoke() gets called on mouse click in action-widget
1879  * call init(), add modal handler
1880  * call apply() with initial motion
1881  *
1882  * modal()  accept modal events while doing it
1883  * call move-areas code with delta motion
1884  * call exit() or cancel() and remove handler
1885  */
1886
1887 typedef struct sAreaSplitData {
1888   int origval;           /* for move areas */
1889   int bigger, smaller;   /* constraints for moving new edge */
1890   int delta;             /* delta move edge */
1891   int origmin, origsize; /* to calculate fac, for property storage */
1892   int previewmode;       /* draw previewline, then split */
1893   void *draw_callback;   /* call `ED_screen_draw_split_preview` */
1894   bool do_snap;
1895
1896   ScrEdge *nedge; /* new edge */
1897   ScrArea *sarea; /* start area */
1898   ScrArea *narea; /* new area */
1899
1900 } sAreaSplitData;
1901
1902 static void area_split_draw_cb(const struct wmWindow *UNUSED(win), void *userdata)
1903 {
1904   const wmOperator *op = userdata;
1905
1906   sAreaSplitData *sd = op->customdata;
1907   if (sd->sarea) {
1908     int dir = RNA_enum_get(op->ptr, "direction");
1909     float fac = RNA_float_get(op->ptr, "factor");
1910
1911     ED_screen_draw_split_preview(sd->sarea, dir, fac);
1912   }
1913 }
1914
1915 /* generic init, menu case, doesn't need active area */
1916 static int area_split_menu_init(bContext *C, wmOperator *op)
1917 {
1918   sAreaSplitData *sd;
1919
1920   /* custom data */
1921   sd = (sAreaSplitData *)MEM_callocN(sizeof(sAreaSplitData), "op_area_split");
1922   op->customdata = sd;
1923
1924   sd->sarea = CTX_wm_area(C);
1925
1926   return 1;
1927 }
1928
1929 /* generic init, no UI stuff here, assumes active area */
1930 static int area_split_init(bContext *C, wmOperator *op)
1931 {
1932   ScrArea *sa = CTX_wm_area(C);
1933   sAreaSplitData *sd;
1934   int areaminy = ED_area_headersize();
1935   int dir;
1936
1937   /* required context */
1938   if (sa == NULL) {
1939     return 0;
1940   }
1941
1942   /* required properties */
1943   dir = RNA_enum_get(op->ptr, "direction");
1944
1945   /* minimal size */
1946   if (dir == 'v' && sa->winx < 2 * AREAMINX) {
1947     return 0;
1948   }
1949   if (dir == 'h' && sa->winy < 2 * areaminy) {
1950     return 0;
1951   }
1952
1953   /* custom data */
1954   sd = (sAreaSplitData *)MEM_callocN(sizeof(sAreaSplitData), "op_area_split");
1955   op->customdata = sd;
1956
1957   sd->sarea = sa;
1958   if (dir == 'v') {
1959     sd->origmin = sa->v1->vec.x;
1960     sd->origsize = sa->v4->vec.x - sd->origmin;
1961   }
1962   else {
1963     sd->origmin = sa->v1->vec.y;
1964     sd->origsize = sa->v2->vec.y - sd->origmin;
1965   }
1966
1967   return 1;
1968 }
1969
1970 /* with sa as center, sb is located at: 0=W, 1=N, 2=E, 3=S */
1971 /* used with split operator */
1972 static ScrEdge *area_findsharededge(bScreen *screen, ScrArea *sa, ScrArea *sb)
1973 {
1974   ScrVert *sav1 = sa->v1;
1975   ScrVert *sav2 = sa->v2;
1976   ScrVert *sav3 = sa->v3;
1977   ScrVert *sav4 = sa->v4;
1978   ScrVert *sbv1 = sb->v1;
1979   ScrVert *sbv2 = sb->v2;
1980   ScrVert *sbv3 = sb->v3;
1981   ScrVert *sbv4 = sb->v4;
1982
1983   if (sav1 == sbv4 && sav2 == sbv3) { /* sa to right of sb = W */
1984     return BKE_screen_find_edge(screen, sav1, sav2);
1985   }
1986   else if (sav2 == sbv1 && sav3 == sbv4) { /* sa to bottom of sb = N */
1987     return BKE_screen_find_edge(screen, sav2, sav3);
1988   }
1989   else if (sav3 == sbv2 && sav4 == sbv1) { /* sa to left of sb = E */
1990     return BKE_screen_find_edge(screen, sav3, sav4);
1991   }
1992   else if (sav1 == sbv2 && sav4 == sbv3) { /* sa on top of sb = S*/
1993     return BKE_screen_find_edge(screen, sav1, sav4);
1994   }
1995
1996   return NULL;
1997 }
1998
1999 /* do the split, return success */
2000 static int area_split_apply(bContext *C, wmOperator *op)
2001 {
2002   const wmWindow *win = CTX_wm_window(C);
2003   bScreen *sc = CTX_wm_screen(C);
2004   sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2005   float fac;
2006   int dir;
2007
2008   fac = RNA_float_get(op->ptr, "factor");
2009   dir = RNA_enum_get(op->ptr, "direction");
2010
2011   sd->narea = area_split(win, sc, sd->sarea, dir, fac, 0); /* 0 = no merge */
2012
2013   if (sd->narea) {
2014     sd->nedge = area_findsharededge(sc, sd->sarea, sd->narea);
2015
2016     /* select newly created edge, prepare for moving edge */
2017     ED_screen_verts_iter(win, sc, sv)
2018     {
2019       sv->editflag = 0;
2020     }
2021
2022     sd->nedge->v1->editflag = 1;
2023     sd->nedge->v2->editflag = 1;
2024
2025     if (dir == 'h') {
2026       sd->origval = sd->nedge->v1->vec.y;
2027     }
2028     else {
2029       sd->origval = sd->nedge->v1->vec.x;
2030     }
2031
2032     ED_area_tag_redraw(sd->sarea);
2033     ED_area_tag_redraw(sd->narea);
2034
2035     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2036     /* Update preview thumbnail */
2037     BKE_icon_changed(sc->id.icon_id);
2038
2039     return 1;
2040   }
2041
2042   return 0;
2043 }
2044
2045 static void area_split_exit(bContext *C, wmOperator *op)
2046 {
2047   if (op->customdata) {
2048     sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2049     if (sd->sarea) {
2050       ED_area_tag_redraw(sd->sarea);
2051     }
2052     if (sd->narea) {
2053       ED_area_tag_redraw(sd->narea);
2054     }
2055
2056     if (sd->draw_callback) {
2057       WM_draw_cb_exit(CTX_wm_window(C), sd->draw_callback);
2058     }
2059
2060     MEM_freeN(op->customdata);
2061     op->customdata = NULL;
2062   }
2063
2064   WM_cursor_modal_restore(CTX_wm_window(C));
2065   WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2066
2067   /* this makes sure aligned edges will result in aligned grabbing */
2068   BKE_screen_remove_double_scrverts(CTX_wm_screen(C));
2069   BKE_screen_remove_double_scredges(CTX_wm_screen(C));
2070 }
2071
2072 static void area_split_preview_update_cursor(bContext *C, wmOperator *op)
2073 {
2074   wmWindow *win = CTX_wm_window(C);
2075   int dir = RNA_enum_get(op->ptr, "direction");
2076   WM_cursor_set(win, (dir == 'n' || dir == 's') ? BC_V_SPLITCURSOR : BC_H_SPLITCURSOR);
2077 }
2078
2079 /* UI callback, adds new handler */
2080 static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2081 {
2082   wmWindow *win = CTX_wm_window(C);
2083   bScreen *sc = CTX_wm_screen(C);
2084   sAreaSplitData *sd;
2085   int dir;
2086
2087   /* no full window splitting allowed */
2088   BLI_assert(sc->state == SCREENNORMAL);
2089
2090   PropertyRNA *prop_dir = RNA_struct_find_property(op->ptr, "direction");
2091   PropertyRNA *prop_factor = RNA_struct_find_property(op->ptr, "factor");
2092   PropertyRNA *prop_cursor = RNA_struct_find_property(op->ptr, "cursor");
2093
2094   if (event->type == EVT_ACTIONZONE_AREA) {
2095     sActionzoneData *sad = event->customdata;
2096
2097     if (sad == NULL || sad->modifier > 0) {
2098       return OPERATOR_PASS_THROUGH;
2099     }
2100
2101     /* verify *sad itself */
2102     if (sad->sa1 == NULL || sad->az == NULL) {
2103       return OPERATOR_PASS_THROUGH;
2104     }
2105
2106     /* is this our *sad? if areas not equal it should be passed on */
2107     if (CTX_wm_area(C) != sad->sa1 || sad->sa1 != sad->sa2) {
2108       return OPERATOR_PASS_THROUGH;
2109     }
2110
2111     /* The factor will be close to 1.0f when near the top-left and the bottom-right corners. */
2112     const float factor_v = ((float)(event->y - sad->sa1->v1->vec.y)) / (float)sad->sa1->winy;
2113     const float factor_h = ((float)(event->x - sad->sa1->v1->vec.x)) / (float)sad->sa1->winx;
2114     const bool is_left = factor_v < 0.5f;
2115     const bool is_bottom = factor_h < 0.5f;
2116     const bool is_right = !is_left;
2117     const bool is_top = !is_bottom;
2118     float factor;
2119
2120     /* Prepare operator state vars. */
2121     if (ELEM(sad->gesture_dir, 'n', 's')) {
2122       dir = 'h';
2123       factor = factor_h;
2124     }
2125     else {
2126       dir = 'v';
2127       factor = factor_v;
2128     }
2129
2130     if ((is_top && is_left) || (is_bottom && is_right)) {
2131       factor = 1.0f - factor;
2132     }
2133
2134     RNA_property_float_set(op->ptr, prop_factor, factor);
2135
2136     RNA_property_enum_set(op->ptr, prop_dir, dir);
2137
2138     /* general init, also non-UI case, adds customdata, sets area and defaults */
2139     if (!area_split_init(C, op)) {
2140       return OPERATOR_PASS_THROUGH;
2141     }
2142   }
2143   else if (RNA_property_is_set(op->ptr, prop_dir)) {
2144     ScrArea *sa = CTX_wm_area(C);
2145     if (sa == NULL) {
2146       return OPERATOR_CANCELLED;
2147     }
2148     dir = RNA_property_enum_get(op->ptr, prop_dir);
2149     if (dir == 'h') {
2150       RNA_property_float_set(
2151           op->ptr, prop_factor, ((float)(event->x - sa->v1->vec.x)) / (float)sa->winx);
2152     }
2153     else {
2154       RNA_property_float_set(
2155           op->ptr, prop_factor, ((float)(event->y - sa->v1->vec.y)) / (float)sa->winy);
2156     }
2157
2158     if (!area_split_init(C, op)) {
2159       return OPERATOR_CANCELLED;
2160     }
2161   }
2162   else {
2163     ScrEdge *actedge;
2164     rcti window_rect;
2165     int event_co[2];
2166
2167     /* retrieve initial mouse coord, so we can find the active edge */
2168     if (RNA_property_is_set(op->ptr, prop_cursor)) {
2169       RNA_property_int_get_array(op->ptr, prop_cursor, event_co);
2170     }
2171     else {
2172       copy_v2_v2_int(event_co, &event->x);
2173     }
2174
2175     WM_window_rect_calc(win, &window_rect);
2176
2177     actedge = screen_geom_area_map_find_active_scredge(
2178         AREAMAP_FROM_SCREEN(sc), &window_rect, event_co[0], event_co[1]);
2179     if (actedge == NULL) {
2180       return OPERATOR_CANCELLED;
2181     }
2182
2183     dir = screen_geom_edge_is_horizontal(actedge) ? 'v' : 'h';
2184
2185     RNA_property_enum_set(op->ptr, prop_dir, dir);
2186
2187     /* special case, adds customdata, sets defaults */
2188     if (!area_split_menu_init(C, op)) {
2189       return OPERATOR_CANCELLED;
2190     }
2191   }
2192
2193   sd = (sAreaSplitData *)op->customdata;
2194
2195   if (event->type == EVT_ACTIONZONE_AREA) {
2196
2197     /* do the split */
2198     if (area_split_apply(C, op)) {
2199       area_move_set_limits(win, sc, dir, &sd->bigger, &sd->smaller, NULL);
2200
2201       /* add temp handler for edge move or cancel */
2202       WM_event_add_modal_handler(C, op);
2203
2204       return OPERATOR_RUNNING_MODAL;
2205     }
2206   }
2207   else {
2208     sd->previewmode = 1;
2209     sd->draw_callback = WM_draw_cb_activate(win, area_split_draw_cb, op);
2210     /* add temp handler for edge move or cancel */
2211     WM_event_add_modal_handler(C, op);
2212     area_split_preview_update_cursor(C, op);
2213
2214     return OPERATOR_RUNNING_MODAL;
2215   }
2216
2217   return OPERATOR_PASS_THROUGH;
2218 }
2219
2220 /* function to be called outside UI context, or for redo */
2221 static int area_split_exec(bContext *C, wmOperator *op)
2222 {
2223
2224   if (!area_split_init(C, op)) {
2225     return OPERATOR_CANCELLED;
2226   }
2227
2228   area_split_apply(C, op);
2229   area_split_exit(C, op);
2230
2231   return OPERATOR_FINISHED;
2232 }
2233
2234 static void area_split_cancel(bContext *C, wmOperator *op)
2235 {
2236   sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2237
2238   if (sd->previewmode) {
2239     /* pass */
2240   }
2241   else {
2242     if (screen_area_join(C, CTX_wm_screen(C), sd->sarea, sd->narea)) {
2243       if (CTX_wm_area(C) == sd->narea) {
2244         CTX_wm_area_set(C, NULL);
2245         CTX_wm_region_set(C, NULL);
2246       }
2247       sd->narea = NULL;
2248     }
2249   }
2250   area_split_exit(C, op);
2251 }
2252
2253 static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event)
2254 {
2255   sAreaSplitData *sd = (sAreaSplitData *)op->customdata;
2256   PropertyRNA *prop_dir = RNA_struct_find_property(op->ptr, "direction");
2257   bool update_factor = false;
2258
2259   /* execute the events */
2260   switch (event->type) {
2261     case MOUSEMOVE:
2262       update_factor = true;
2263       break;
2264
2265     case LEFTMOUSE:
2266       if (sd->previewmode) {
2267         area_split_apply(C, op);
2268         area_split_exit(C, op);
2269         return OPERATOR_FINISHED;
2270       }
2271       else {
2272         if (event->val == KM_RELEASE) { /* mouse up */
2273           area_split_exit(C, op);
2274           return OPERATOR_FINISHED;
2275         }
2276       }
2277       break;
2278
2279     case MIDDLEMOUSE:
2280     case TABKEY:
2281       if (sd->previewmode == 0) {
2282         /* pass */
2283       }
2284       else {
2285         if (event->val == KM_PRESS) {
2286           if (sd->sarea) {
2287             int dir = RNA_property_enum_get(op->ptr, prop_dir);
2288             RNA_property_enum_set(op->ptr, prop_dir, (dir == 'v') ? 'h' : 'v');
2289             area_split_preview_update_cursor(C, op);
2290             update_factor = true;
2291           }
2292         }
2293       }
2294
2295       break;
2296
2297     case RIGHTMOUSE: /* cancel operation */
2298     case ESCKEY:
2299       area_split_cancel(C, op);
2300       return OPERATOR_CANCELLED;
2301
2302     case LEFTCTRLKEY:
2303       sd->do_snap = event->val == KM_PRESS;
2304       update_factor = true;
2305       break;
2306   }
2307
2308   if (update_factor) {
2309     const int dir = RNA_property_enum_get(op->ptr, prop_dir);
2310
2311     sd->delta = (dir == 'v') ? event->x - sd->origval : event->y - sd->origval;
2312
2313     if (sd->previewmode == 0) {
2314       if (sd->do_snap) {
2315         const int snap_loc = area_snap_calc_location(CTX_wm_screen(C),
2316                                                      SNAP_FRACTION_AND_ADJACENT,
2317                                                      sd->delta,
2318                                                      sd->origval,
2319                                                      dir,
2320                                                      sd->bigger,
2321                                                      sd->smaller);
2322         sd->delta = snap_loc - sd->origval;
2323       }
2324       area_move_apply_do(C, sd->delta, sd->origval, dir, sd->bigger, sd->smaller, SNAP_NONE);
2325     }
2326     else {
2327       if (sd->sarea) {
2328         ED_area_tag_redraw(sd->sarea);
2329       }
2330       /* area context not set */
2331       sd->sarea = BKE_screen_find_area_xy(CTX_wm_screen(C), SPACE_TYPE_ANY, event->x, event->y);
2332
2333       if (sd->sarea) {
2334         ScrArea *sa = sd->sarea;
2335         if (dir == 'v') {
2336           sd->origmin = sa->v1->vec.x;
2337           sd->origsize = sa->v4->vec.x - sd->origmin;
2338         }
2339         else {
2340           sd->origmin = sa->v1->vec.y;
2341           sd->origsize = sa->v2->vec.y - sd->origmin;
2342         }
2343
2344         if (sd->do_snap) {
2345           sa->v1->editflag = sa->v2->editflag = sa->v3->editflag = sa->v4->editflag = 1;
2346
2347           const int snap_loc = area_snap_calc_location(CTX_wm_screen(C),
2348                                                        SNAP_FRACTION_AND_ADJACENT,
2349                                                        sd->delta,
2350                                                        sd->origval,
2351                                                        dir,
2352                                                        sd->origmin + sd->origsize,
2353                                                        -sd->origmin);
2354
2355           sa->v1->editflag = sa->v2->editflag = sa->v3->editflag = sa->v4->editflag = 0;
2356           sd->delta = snap_loc - sd->origval;
2357         }
2358
2359         ED_area_tag_redraw(sd->sarea);
2360       }
2361
2362       CTX_wm_screen(C)->do_draw = true;
2363     }
2364
2365     float fac = (float)(sd->delta + sd->origval - sd->origmin) / sd->origsize;
2366     RNA_float_set(op->ptr, "factor", fac);
2367   }
2368
2369   return OPERATOR_RUNNING_MODAL;
2370 }
2371
2372 static const EnumPropertyItem prop_direction_items[] = {
2373     {'h', "HORIZONTAL", 0, "Horizontal", ""},
2374     {'v', "VERTICAL", 0, "Vertical", ""},
2375     {0, NULL, 0, NULL, NULL},
2376 };
2377
2378 static void SCREEN_OT_area_split(wmOperatorType *ot)
2379 {
2380   ot->name = "Split Area";
2381   ot->description = "Split selected area into new windows";
2382   ot->idname = "SCREEN_OT_area_split";
2383
2384   ot->exec = area_split_exec;
2385   ot->invoke = area_split_invoke;
2386   ot->modal = area_split_modal;
2387   ot->cancel = area_split_cancel;
2388
2389   ot->poll = screen_active_editable;
2390
2391   /* flags */
2392   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
2393
2394   /* rna */
2395   RNA_def_enum(ot->srna, "direction", prop_direction_items, 'h', "Direction", "");
2396   RNA_def_float(ot->srna, "factor", 0.5f, 0.0, 1.0, "Factor", "", 0.0, 1.0);
2397   RNA_def_int_vector(
2398       ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX);
2399 }
2400
2401 /** \} */
2402
2403 /* -------------------------------------------------------------------- */
2404 /** \name Scale Region Edge Operator
2405  * \{ */
2406
2407 typedef struct RegionMoveData {
2408   AZone *az;
2409   ARegion *ar;
2410   ScrArea *sa;
2411   int bigger, smaller, origval;
2412   int origx, origy;
2413   int maxsize;
2414   AZEdge edge;
2415
2416 } RegionMoveData;
2417
2418 static int area_max_regionsize(ScrArea *sa, ARegion *scalear, AZEdge edge)
2419 {
2420   int dist;
2421
2422   /* regions in regions. */
2423   if (scalear->alignment & RGN_SPLIT_PREV) {
2424     const int align = RGN_ALIGN_ENUM_FROM_MASK(scalear->alignment);
2425
2426     if (ELEM(align, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
2427       ARegion *ar = scalear->prev;
2428       dist = ar->winy + scalear->winy - U.pixelsize;
2429     }
2430     else /* if (ELEM(align, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) */ {
2431       ARegion *ar = scalear->prev;
2432       dist = ar->winx + scalear->winx - U.pixelsize;
2433     }
2434   }
2435   else {
2436     if (edge == AE_RIGHT_TO_TOPLEFT || edge == AE_LEFT_TO_TOPRIGHT) {
2437       dist = BLI_rcti_size_x(&sa->totrct);
2438     }
2439     else { /* AE_BOTTOM_TO_TOPLEFT, AE_TOP_TO_BOTTOMRIGHT */
2440       dist = BLI_rcti_size_y(&sa->totrct);
2441     }
2442
2443     /* subtractwidth of regions on opposite side
2444      * prevents dragging regions into other opposite regions */
2445     for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
2446       if (ar == scalear) {
2447         continue;
2448       }
2449
2450       if (scalear->alignment == RGN_ALIGN_LEFT && ar->alignment == RGN_ALIGN_RIGHT) {
2451         dist -= ar->winx;
2452       }
2453       else if (scalear->alignment == RGN_ALIGN_RIGHT && ar->alignment == RGN_ALIGN_LEFT) {
2454         dist -= ar->winx;
2455       }
2456       else if (scalear->alignment == RGN_ALIGN_TOP &&
2457                (ar->alignment == RGN_ALIGN_BOTTOM ||
2458                 ELEM(ar->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) {
2459         dist -= ar->winy;
2460       }
2461       else if (scalear->alignment == RGN_ALIGN_BOTTOM &&
2462                (ar->alignment == RGN_ALIGN_TOP ||
2463                 ELEM(ar->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER, RGN_TYPE_FOOTER))) {
2464         dist -= ar->winy;
2465       }
2466     }
2467   }
2468
2469   dist /= UI_DPI_FAC;
2470   return dist;
2471 }
2472
2473 static int region_scale_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2474 {
2475   sActionzoneData *sad = event->customdata;
2476   AZone *az;
2477
2478   if (event->type != EVT_ACTIONZONE_REGION) {
2479     BKE_report(op->reports, RPT_ERROR, "Can only scale region size from an action zone");
2480     return OPERATOR_CANCELLED;
2481   }
2482
2483   az = sad->az;
2484
2485   if (az->ar) {
2486     RegionMoveData *rmd = MEM_callocN(sizeof(RegionMoveData), "RegionMoveData");
2487
2488     op->customdata = rmd;
2489
2490     rmd->az = az;
2491     rmd->ar = az->ar;
2492     rmd->sa = sad->sa1;
2493     rmd->edge = az->edge;
2494     rmd->origx = event->x;
2495     rmd->origy = event->y;
2496     rmd->maxsize = area_max_regionsize(rmd->sa, rmd->ar, rmd->edge);
2497
2498     /* if not set we do now, otherwise it uses type */
2499     if (rmd->ar->sizex == 0) {
2500       rmd->ar->sizex = rmd->ar->winx;
2501     }
2502     if (rmd->ar->sizey == 0) {
2503       rmd->ar->sizey = rmd->ar->winy;
2504     }
2505
2506     /* now copy to regionmovedata */
2507     if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2508       rmd->origval = rmd->ar->sizex;
2509     }
2510     else {
2511       rmd->origval = rmd->ar->sizey;
2512     }
2513
2514     CLAMP(rmd->maxsize, 0, 1000);
2515
2516     /* add temp handler */
2517     WM_event_add_modal_handler(C, op);
2518
2519     return OPERATOR_RUNNING_MODAL;
2520   }
2521
2522   return OPERATOR_FINISHED;
2523 }
2524
2525 static void region_scale_validate_size(RegionMoveData *rmd)
2526 {
2527   if ((rmd->ar->flag & RGN_FLAG_HIDDEN) == 0) {
2528     short *size, maxsize = -1;
2529
2530     if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2531       size = &rmd->ar->sizex;
2532     }
2533     else {
2534       size = &rmd->ar->sizey;
2535     }
2536
2537     maxsize = rmd->maxsize - (UI_UNIT_Y / UI_DPI_FAC);
2538
2539     if (*size > maxsize && maxsize > 0) {
2540       *size = maxsize;
2541     }
2542   }
2543 }
2544
2545 static void region_scale_toggle_hidden(bContext *C, RegionMoveData *rmd)
2546 {
2547   /* hidden areas may have bad 'View2D.cur' value,
2548    * correct before displaying. see T45156 */
2549   if (rmd->ar->flag & RGN_FLAG_HIDDEN) {
2550     UI_view2d_curRect_validate(&rmd->ar->v2d);
2551   }
2552
2553   region_toggle_hidden(C, rmd->ar, 0);
2554   region_scale_validate_size(rmd);
2555
2556   if ((rmd->ar->flag & RGN_FLAG_HIDDEN) == 0) {
2557     if (rmd->ar->regiontype == RGN_TYPE_HEADER) {
2558       ARegion *ar_tool_header = BKE_area_find_region_type(rmd->sa, RGN_TYPE_TOOL_HEADER);
2559       if (ar_tool_header != NULL) {
2560         if ((ar_tool_header->flag & RGN_FLAG_HIDDEN_BY_USER) == 0 &&
2561             (ar_tool_header->flag & RGN_FLAG_HIDDEN) != 0) {
2562           region_toggle_hidden(C, ar_tool_header, 0);
2563         }
2564       }
2565     }
2566   }
2567 }
2568
2569 static int region_scale_modal(bContext *C, wmOperator *op, const wmEvent *event)
2570 {
2571   RegionMoveData *rmd = op->customdata;
2572   int delta;
2573
2574   /* execute the events */
2575   switch (event->type) {
2576     case MOUSEMOVE: {
2577       const float aspect = BLI_rctf_size_x(&rmd->ar->v2d.cur) /
2578                            (BLI_rcti_size_x(&rmd->ar->v2d.mask) + 1);
2579       const int snap_size_threshold = (U.widget_unit * 2) / aspect;
2580       if (rmd->edge == AE_LEFT_TO_TOPRIGHT || rmd->edge == AE_RIGHT_TO_TOPLEFT) {
2581         delta = event->x - rmd->origx;
2582         if (rmd->edge == AE_LEFT_TO_TOPRIGHT) {
2583           delta = -delta;
2584         }
2585
2586         /* region sizes now get multiplied */
2587         delta /= UI_DPI_FAC;
2588
2589         rmd->ar->sizex = rmd->origval + delta;
2590
2591         if (rmd->ar->type->snap_size) {
2592           short sizex_test = rmd->ar->type->snap_size(rmd->ar, rmd->ar->sizex, 0);
2593           if (ABS(rmd->ar->sizex - sizex_test) < snap_size_threshold) {
2594             rmd->ar->sizex = sizex_test;
2595           }
2596         }
2597         CLAMP(rmd->ar->sizex, 0, rmd->maxsize);
2598
2599         if (rmd->ar->sizex < UI_UNIT_X) {
2600           rmd->ar->sizex = rmd->origval;
2601           if (!(rmd->ar->flag & RGN_FLAG_HIDDEN)) {
2602             region_scale_toggle_hidden(C, rmd);
2603           }
2604         }
2605         else if (rmd->ar->flag & RGN_FLAG_HIDDEN) {
2606           region_scale_toggle_hidden(C, rmd);
2607         }
2608         else if (rmd->ar->flag & RGN_FLAG_DYNAMIC_SIZE) {
2609           rmd->ar->sizex = rmd->origval;
2610         }
2611       }
2612       else {
2613         delta = event->y - rmd->origy;
2614         if (rmd->edge == AE_BOTTOM_TO_TOPLEFT) {
2615           delta = -delta;
2616         }
2617
2618         /* region sizes now get multiplied */
2619         delta /= UI_DPI_FAC;
2620
2621         rmd->ar->sizey = rmd->origval + delta;
2622
2623         if (rmd->ar->type->snap_size) {
2624           short sizey_test = rmd->ar->type->snap_size(rmd->ar, rmd->ar->sizey, 1);
2625           if (ABS(rmd->ar->sizey - sizey_test) < snap_size_threshold) {
2626             rmd->ar->sizey = sizey_test;
2627           }
2628         }
2629         CLAMP(rmd->ar->sizey, 0, rmd->maxsize);
2630
2631         /* note, 'UI_UNIT_Y/4' means you need to drag the footer and execute region
2632          * almost all the way down for it to become hidden, this is done
2633          * otherwise its too easy to do this by accident */
2634         if (rmd->ar->sizey < UI_UNIT_Y / 4) {
2635           rmd->ar->sizey = rmd->origval;
2636           if (!(rmd->ar->flag & RGN_FLAG_HIDDEN)) {
2637             region_scale_toggle_hidden(C, rmd);
2638           }
2639         }
2640         else if (rmd->ar->flag & RGN_FLAG_HIDDEN) {
2641           region_scale_toggle_hidden(C, rmd);
2642         }
2643         else if (rmd->ar->flag & RGN_FLAG_DYNAMIC_SIZE) {
2644           rmd->ar->sizey = rmd->origval;
2645         }
2646       }
2647       ED_area_tag_redraw(rmd->sa);
2648       WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2649
2650       break;
2651     }
2652     case LEFTMOUSE:
2653       if (event->val == KM_RELEASE) {
2654         if (len_manhattan_v2v2_int(&event->x, &rmd->origx) <= WM_EVENT_CURSOR_MOTION_THRESHOLD) {
2655           if (rmd->ar->flag & RGN_FLAG_HIDDEN) {
2656             region_scale_toggle_hidden(C, rmd);
2657           }
2658           else if (rmd->ar->flag & RGN_FLAG_TOO_SMALL) {
2659             region_scale_validate_size(rmd);
2660           }
2661
2662           ED_area_tag_redraw(rmd->sa);
2663           WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2664         }
2665         MEM_freeN(op->customdata);
2666         op->customdata = NULL;
2667
2668         return OPERATOR_FINISHED;
2669       }
2670       break;
2671
2672     case ESCKEY:
2673       break;
2674   }
2675
2676   return OPERATOR_RUNNING_MODAL;
2677 }
2678
2679 static void region_scale_cancel(bContext *UNUSED(C), wmOperator *op)
2680 {
2681   MEM_freeN(op->customdata);
2682   op->customdata = NULL;
2683 }
2684
2685 static void SCREEN_OT_region_scale(wmOperatorType *ot)
2686 {
2687   /* identifiers */
2688   ot->name = "Scale Region Size";
2689   ot->description = "Scale selected area";
2690   ot->idname = "SCREEN_OT_region_scale";
2691
2692   ot->invoke = region_scale_invoke;
2693   ot->modal = region_scale_modal;
2694   ot->cancel = region_scale_cancel;
2695
2696   ot->poll = ED_operator_areaactive;
2697
2698   /* flags */
2699   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
2700 }
2701
2702 /** \} */
2703
2704 /* -------------------------------------------------------------------- */
2705 /** \name Frame Change Operator
2706  * \{ */
2707
2708 static void areas_do_frame_follow(bContext *C, bool middle)
2709 {
2710   bScreen *scr = CTX_wm_screen(C);
2711   Scene *scene = CTX_data_scene(C);
2712   wmWindowManager *wm = CTX_wm_manager(C);
2713   for (wmWindow *window = wm->windows.first; window; window = window->next) {
2714     const bScreen *screen = WM_window_get_active_screen(window);
2715
2716     for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
2717       for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
2718         /* do follow here if editor type supports it */
2719         if ((scr->redraws_flag & TIME_FOLLOW)) {
2720           if ((ar->regiontype == RGN_TYPE_WINDOW &&
2721                ELEM(sa->spacetype, SPACE_SEQ, SPACE_GRAPH, SPACE_ACTION, SPACE_NLA)) ||
2722               (sa->spacetype == SPACE_CLIP && ar->regiontype == RGN_TYPE_PREVIEW)) {
2723             float w = BLI_rctf_size_x(&ar->v2d.cur);
2724
2725             if (middle) {
2726               if ((scene->r.cfra < ar->v2d.cur.xmin) || (scene->r.cfra > ar->v2d.cur.xmax)) {
2727                 ar->v2d.cur.xmax = scene->r.cfra + (w / 2);
2728                 ar->v2d.cur.xmin = scene->r.cfra - (w / 2);
2729               }
2730             }
2731             else {
2732               if (scene->r.cfra < ar->v2d.cur.xmin) {
2733                 ar->v2d.cur.xmax = scene->r.cfra;
2734                 ar->v2d.cur.xmin = ar->v2d.cur.xmax - w;
2735               }
2736               else if (scene->r.cfra > ar->v2d.cur.xmax) {
2737                 ar->v2d.cur.xmin = scene->r.cfra;
2738                 ar->v2d.cur.xmax = ar->v2d.cur.xmin + w;
2739               }
2740             }
2741           }
2742         }
2743       }
2744     }
2745   }
2746 }
2747
2748 /* function to be called outside UI context, or for redo */
2749 static int frame_offset_exec(bContext *C, wmOperator *op)
2750 {
2751   Scene *scene = CTX_data_scene(C);
2752   int delta;
2753
2754   delta = RNA_int_get(op->ptr, "delta");
2755
2756   CFRA += delta;
2757   FRAMENUMBER_MIN_CLAMP(CFRA);
2758   SUBFRA = 0.f;
2759
2760   areas_do_frame_follow(C, false);
2761
2762   DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
2763
2764   WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
2765
2766   return OPERATOR_FINISHED;
2767 }
2768
2769 static void SCREEN_OT_frame_offset(wmOperatorType *ot)
2770 {
2771   ot->name = "Frame Offset";
2772   ot->idname = "SCREEN_OT_frame_offset";
2773   ot->description = "Move current frame forward/backward by a given number";
2774
2775   ot->exec = frame_offset_exec;
2776
2777   ot->poll = ED_operator_screenactive_norender;
2778   ot->flag = OPTYPE_UNDO_GROUPED;
2779   ot->undo_group = "Frame Change";
2780
2781   /* rna */
2782   RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
2783 }
2784
2785 /** \} */
2786
2787 /* -------------------------------------------------------------------- */
2788 /** \name Frame Jump Operator
2789  * \{ */
2790
2791 /* function to be called outside UI context, or for redo */
2792 static int frame_jump_exec(bContext *C, wmOperator *op)
2793 {
2794   Scene *scene = CTX_data_scene(C);
2795   wmTimer *animtimer = CTX_wm_screen(C)->animtimer;
2796
2797   /* Don't change CFRA directly if animtimer is running as this can cause
2798    * first/last frame not to be actually shown (bad since for example physics
2799    * simulations aren't reset properly).
2800    */
2801   if (animtimer) {
2802     ScreenAnimData *sad = animtimer->customdata;
2803
2804     sad->flag |= ANIMPLAY_FLAG_USE_NEXT_FRAME;
2805
2806     if (RNA_boolean_get(op->ptr, "end")) {
2807       sad->nextfra = PEFRA;
2808     }
2809     else {
2810       sad->nextfra = PSFRA;
2811     }
2812   }
2813   else {
2814     if (RNA_boolean_get(op->ptr, "end")) {
2815       CFRA = PEFRA;
2816     }
2817     else {
2818       CFRA = PSFRA;
2819     }
2820
2821     areas_do_frame_follow(C, true);
2822
2823     DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
2824
2825     WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
2826   }
2827
2828   return OPERATOR_FINISHED;
2829 }
2830
2831 static void SCREEN_OT_frame_jump(wmOperatorType *ot)
2832 {
2833   ot->name = "Jump to Endpoint";
2834   ot->description = "Jump to first/last frame in frame range";
2835   ot->idname = "SCREEN_OT_frame_jump";
2836
2837   ot->exec = frame_jump_exec;
2838
2839   ot->poll = ED_operator_screenactive_norender;
2840   ot->flag = OPTYPE_UNDO_GROUPED;
2841   ot->undo_group = "Frame Change";
2842
2843   /* rna */
2844   RNA_def_boolean(ot->srna, "end", 0, "Last Frame", "Jump to the last frame of the frame range");
2845 }
2846
2847 /** \} */
2848
2849 /* -------------------------------------------------------------------- */
2850 /** \name Jump to Key-Frame Operator
2851  * \{ */
2852
2853 /* function to be called outside UI context, or for redo */
2854 static int keyframe_jump_exec(bContext *C, wmOperator *op)
2855 {
2856   Scene *scene = CTX_data_scene(C);
2857   Object *ob = CTX_data_active_object(C);
2858   bDopeSheet ads = {NULL};
2859   DLRBT_Tree keys;
2860   ActKeyColumn *ak;
2861   float cfra;
2862   const bool next = RNA_boolean_get(op->ptr, "next");
2863   bool done = false;
2864
2865   /* sanity checks */
2866   if (scene == NULL) {
2867     return OPERATOR_CANCELLED;
2868   }
2869
2870   cfra = (float)(CFRA);
2871
2872   /* init binarytree-list for getting keyframes */
2873   BLI_dlrbTree_init(&keys);
2874
2875   /* seed up dummy dopesheet context with flags to perform necessary filtering */
2876   if ((scene->flag & SCE_KEYS_NO_SELONLY) == 0) {
2877     /* only selected channels are included */
2878     ads.filterflag |= ADS_FILTER_ONLYSEL;
2879   }
2880
2881   /* populate tree with keyframe nodes */
2882   scene_to_keylist(&ads, scene, &keys, 0);
2883
2884   if (ob) {
2885     ob_to_keylist(&ads, ob, &keys, 0);
2886
2887     if (ob->type == OB_GPENCIL) {
2888       const bool active = !(scene->flag & SCE_KEYS_NO_SELONLY);
2889       gpencil_to_keylist(&ads, ob->data, &keys, active);
2890     }
2891   }
2892
2893   {
2894     Mask *mask = CTX_data_edit_mask(C);
2895     if (mask) {
2896       MaskLayer *masklay = BKE_mask_layer_active(mask);
2897       mask_to_keylist(&ads, masklay, &keys);
2898     }
2899   }
2900
2901   /* find matching keyframe in the right direction */
2902   if (next) {
2903     ak = (ActKeyColumn *)BLI_dlrbTree_search_next(&keys, compare_ak_cfraPtr, &cfra);
2904   }
2905   else {
2906     ak = (ActKeyColumn *)BLI_dlrbTree_search_prev(&keys, compare_ak_cfraPtr, &cfra);
2907   }
2908
2909   while ((ak != NULL) && (done == false)) {
2910     if (CFRA != (int)ak->cfra) {
2911       /* this changes the frame, so set the frame and we're done */
2912       CFRA = (int)ak->cfra;
2913       done = true;
2914     }
2915     else {
2916       /* take another step... */
2917       if (next) {
2918         ak = ak->next;
2919       }
2920       else {
2921         ak = ak->prev;
2922       }
2923     }
2924   }
2925
2926   /* free temp stuff */
2927   BLI_dlrbTree_free(&keys);
2928
2929   /* any success? */
2930   if (done == false) {
2931     BKE_report(op->reports, RPT_INFO, "No more keyframes to jump to in this direction");
2932
2933     return OPERATOR_CANCELLED;
2934   }
2935   else {
2936     areas_do_frame_follow(C, true);
2937
2938     DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
2939
2940     WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
2941
2942     return OPERATOR_FINISHED;
2943   }
2944 }
2945
2946 static void SCREEN_OT_keyframe_jump(wmOperatorType *ot)
2947 {
2948   ot->name = "Jump to Keyframe";
2949   ot->description = "Jump to previous/next keyframe";
2950   ot->idname = "SCREEN_OT_keyframe_jump";
2951
2952   ot->exec = keyframe_jump_exec;
2953
2954   ot->poll = ED_operator_screenactive_norender;
2955   ot->flag = OPTYPE_UNDO_GROUPED;
2956   ot->undo_group = "Frame Change";
2957
2958   /* properties */
2959   RNA_def_boolean(ot->srna, "next", true, "Next Keyframe", "");
2960 }
2961
2962 /** \} */
2963
2964 /* -------------------------------------------------------------------- */
2965 /** \name Jump to Marker Operator
2966  * \{ */
2967
2968 /* function to be called outside UI context, or for redo */
2969 static int marker_jump_exec(bContext *C, wmOperator *op)
2970 {
2971   Scene *scene = CTX_data_scene(C);
2972   TimeMarker *marker;
2973   int closest = CFRA;
2974   const bool next = RNA_boolean_get(op->ptr, "next");
2975   bool found = false;
2976
2977   /* find matching marker in the right direction */
2978   for (marker = scene->markers.first; marker; marker = marker->next) {
2979     if (next) {
2980       if ((marker->frame > CFRA) && (!found || closest > marker->frame)) {
2981         closest = marker->frame;
2982         found = true;
2983       }
2984     }
2985     else {
2986       if ((marker->frame < CFRA) && (!found || closest < marker->frame)) {
2987         closest = marker->frame;
2988         found = true;
2989       }
2990     }
2991   }
2992
2993   /* any success? */
2994   if (!found) {
2995     BKE_report(op->reports, RPT_INFO, "No more markers to jump to in this direction");
2996
2997     return OPERATOR_CANCELLED;
2998   }
2999   else {
3000     CFRA = closest;
3001
3002     areas_do_frame_follow(C, true);
3003
3004     DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_SEEK);
3005
3006     WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
3007
3008     return OPERATOR_FINISHED;
3009   }
3010 }
3011
3012 static void SCREEN_OT_marker_jump(wmOperatorType *ot)
3013 {
3014   ot->name = "Jump to Marker";
3015   ot->description = "Jump to previous/next marker";
3016   ot->idname = "SCREEN_OT_marker_jump";
3017
3018   ot->exec = marker_jump_exec;
3019
3020   ot->poll = ED_operator_screenactive_norender;
3021   ot->flag = OPTYPE_UNDO_GROUPED;
3022   ot->undo_group = "Frame Change";
3023
3024   /* properties */
3025   RNA_def_boolean(ot->srna, "next", true, "Next Marker", "");
3026 }
3027
3028 /** \} */
3029
3030 /* -------------------------------------------------------------------- */
3031 /** \name Set Screen Operator
3032  * \{ */
3033
3034 /* function to be called outside UI context, or for redo */
3035 static int screen_set_exec(bContext *C, wmOperator *op)
3036 {
3037   WorkSpace *workspace = CTX_wm_workspace(C);
3038   int delta = RNA_int_get(op->ptr, "delta");
3039
3040   if (ED_workspace_layout_cycle(workspace, delta, C)) {
3041     return OPERATOR_FINISHED;
3042   }
3043
3044   return OPERATOR_CANCELLED;
3045 }
3046
3047 static void SCREEN_OT_screen_set(wmOperatorType *ot)
3048 {
3049   ot->name = "Set Screen";
3050   ot->description = "Cycle through available screens";
3051   ot->idname = "SCREEN_OT_screen_set";
3052
3053   ot->exec = screen_set_exec;
3054   ot->poll = ED_operator_screenactive;
3055
3056   /* rna */
3057   RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
3058 }
3059
3060 /** \} */
3061
3062 /* -------------------------------------------------------------------- */
3063 /** \name Screen Full-Area Operator
3064  * \{ */
3065
3066 /* function to be called outside UI context, or for redo */
3067 static int screen_maximize_area_exec(bContext *C, wmOperator *op)
3068 {
3069   bScreen *screen = CTX_wm_screen(C);
3070   ScrArea *sa = NULL;
3071   const bool hide_panels = RNA_boolean_get(op->ptr, "use_hide_panels");
3072
3073   /* search current screen for 'fullscreen' areas */
3074   /* prevents restoring info header, when mouse is over it */
3075   for (sa = screen->areabase.first; sa; sa = sa->next) {
3076     if (sa->full) {
3077       break;
3078     }
3079   }
3080
3081   if (sa == NULL) {
3082     sa = CTX_wm_area(C);
3083   }
3084
3085   if (hide_panels) {
3086     if (!ELEM(screen->state, SCREENNORMAL, SCREENFULL)) {
3087       return OPERATOR_CANCELLED;
3088     }
3089     ED_screen_state_toggle(C, CTX_wm_window(C), sa, SCREENFULL);
3090   }
3091   else {
3092     if (!ELEM(screen->state, SCREENNORMAL, SCREENMAXIMIZED)) {
3093       return OPERATOR_CANCELLED;
3094     }
3095     ED_screen_state_toggle(C, CTX_wm_window(C), sa, SCREENMAXIMIZED);
3096   }
3097
3098   return OPERATOR_FINISHED;
3099 }
3100
3101 static bool screen_maximize_area_poll(bContext *C)
3102 {
3103   const bScreen *screen = CTX_wm_screen(C);
3104   const ScrArea *area = CTX_wm_area(C);
3105   return ED_operator_areaactive(C) &&
3106          /* Don't allow maximizing global areas but allow minimizing from them. */
3107          ((screen->state != SCREENNORMAL) || !ED_area_is_global(area));
3108 }
3109
3110 static void SCREEN_OT_screen_full_area(wmOperatorType *ot)
3111 {
3112   PropertyRNA *prop;
3113
3114   ot->name = "Toggle Maximize Area";
3115   ot->description = "Toggle display selected area as fullscreen/maximized";
3116   ot->idname = "SCREEN_OT_screen_full_area";
3117
3118   ot->exec = screen_maximize_area_exec;
3119   ot->poll = screen_maximize_area_poll;
3120   ot->flag = 0;
3121
3122   prop = RNA_def_boolean(ot->srna, "use_hide_panels", false, "Hide Panels", "Hide all the panels");
3123   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3124 }
3125
3126 /** \} */
3127
3128 /* -------------------------------------------------------------------- */
3129 /** \name Screen Join-Area Operator
3130  * \{ */
3131
3132 /* operator state vars used:
3133  * x1, y1     mouse coord in first area, which will disappear
3134  * x2, y2     mouse coord in 2nd area, which will become joined
3135  *
3136  * functions:
3137  *
3138  * init()   find edge based on state vars
3139  * test if the edge divides two areas,
3140  * store active and nonactive area,
3141  *
3142  * apply()  do the actual join
3143  *
3144  * exit()   cleanup, send notifier
3145  *
3146  * callbacks:
3147  *
3148  * exec()   calls init, apply, exit
3149  *
3150  * invoke() sets mouse coords in x,y
3151  * call init()
3152  * add modal handler
3153  *
3154  * modal()  accept modal events while doing it
3155  * call apply() with active window and nonactive window
3156  * call exit() and remove handler when LMB confirm
3157  */
3158
3159 typedef struct sAreaJoinData {
3160   ScrArea *sa1;        /* first area to be considered */
3161   ScrArea *sa2;        /* second area to be considered */
3162   void *draw_callback; /* call `ED_screen_draw_join_shape` */
3163
3164 } sAreaJoinData;
3165
3166 static void area_join_draw_cb(const struct wmWindow *UNUSED(win), void *userdata)
3167 {
3168   const wmOperator *op = userdata;
3169
3170   sAreaJoinData *sd = op->customdata;
3171   if (sd->sa1 && sd->sa2) {
3172     ED_screen_draw_join_shape(sd->sa1, sd->sa2);
3173   }
3174 }
3175
3176 /* validate selection inside screen, set variables OK */
3177 /* return 0: init failed */
3178 /* XXX todo: find edge based on (x,y) and set other area? */
3179 static int area_join_init(bContext *C, wmOperator *op)
3180 {
3181   const wmWindow *win = CTX_wm_window(C);
3182   bScreen *screen = CTX_wm_screen(C);
3183   ScrArea *sa1, *sa2;
3184   sAreaJoinData *jd = NULL;
3185   int x1, y1;
3186   int x2, y2;
3187
3188   /* required properties, make negative to get return 0 if not set by caller */
3189   x1 = RNA_int_get(op->ptr, "min_x");
3190   y1 = RNA_int_get(op->ptr, "min_y");
3191   x2 = RNA_int_get(op->ptr, "max_x");
3192   y2 = RNA_int_get(op->ptr, "max_y");
3193
3194   sa1 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, x1, y1);
3195   if (sa1 == NULL) {
3196     sa1 = BKE_screen_area_map_find_area_xy(&win->global_areas, SPACE_TYPE_ANY, x1, y1);
3197   }
3198   sa2 = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, x2, y2);
3199   if (sa2 == NULL) {
3200     sa2 = BKE_screen_area_map_find_area_xy(&win->global_areas, SPACE_TYPE_ANY, x2, y2);
3201   }
3202   if ((sa1 && ED_area_is_global(sa1)) || (sa2 && ED_area_is_global(sa2))) {
3203     BKE_report(
3204         op->reports, RPT_ERROR, "Global areas (Top Bar, Status Bar) do not support joining");
3205     return 0;
3206   }
3207   else if (sa1 == NULL || sa2 == NULL || sa1 == sa2) {
3208     return 0;
3209   }
3210
3211   jd = (sAreaJoinData *)MEM_callocN(sizeof(sAreaJoinData), "op_area_join");
3212
3213   jd->sa1 = sa1;
3214   jd->sa2 = sa2;
3215
3216   op->customdata = jd;
3217
3218   jd->draw_callback = WM_draw_cb_activate(CTX_wm_window(C), area_join_draw_cb, op);
3219
3220   return 1;
3221 }
3222
3223 /* apply the join of the areas (space types) */
3224 static int area_join_apply(bContext *C, wmOperator *op)
3225 {
3226   sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3227   if (!jd) {
3228     return 0;
3229   }
3230
3231   if (!screen_area_join(C, CTX_wm_screen(C), jd->sa1, jd->sa2)) {
3232     return 0;
3233   }
3234   if (CTX_wm_area(C) == jd->sa2) {
3235     CTX_wm_area_set(C, NULL);
3236     CTX_wm_region_set(C, NULL);
3237   }
3238
3239   return 1;
3240 }
3241
3242 /* finish operation */
3243 static void area_join_exit(bContext *C, wmOperator *op)
3244 {
3245   sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3246
3247   if (jd) {
3248     if (jd->draw_callback) {
3249       WM_draw_cb_exit(CTX_wm_window(C), jd->draw_callback);
3250     }
3251
3252     MEM_freeN(jd);
3253     op->customdata = NULL;
3254   }
3255
3256   /* this makes sure aligned edges will result in aligned grabbing */
3257   BKE_screen_remove_double_scredges(CTX_wm_screen(C));
3258   BKE_screen_remove_unused_scredges(CTX_wm_screen(C));
3259   BKE_screen_remove_unused_scrverts(CTX_wm_screen(C));
3260 }
3261
3262 static int area_join_exec(bContext *C, wmOperator *op)
3263 {
3264   if (!area_join_init(C, op)) {
3265     return OPERATOR_CANCELLED;
3266   }
3267
3268   area_join_apply(C, op);
3269   area_join_exit(C, op);
3270
3271   return OPERATOR_FINISHED;
3272 }
3273
3274 /* interaction callback */
3275 static int area_join_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3276 {
3277
3278   if (event->type == EVT_ACTIONZONE_AREA) {
3279     sActionzoneData *sad = event->customdata;
3280
3281     if (sad == NULL || sad->modifier > 0) {
3282       return OPERATOR_PASS_THROUGH;
3283     }
3284
3285     /* verify *sad itself */
3286     if (sad->sa1 == NULL || sad->sa2 == NULL) {
3287       return OPERATOR_PASS_THROUGH;
3288     }
3289
3290     /* is this our *sad? if areas equal it should be passed on */
3291     if (sad->sa1 == sad->sa2) {
3292       return OPERATOR_PASS_THROUGH;
3293     }
3294
3295     /* prepare operator state vars */
3296     RNA_int_set(op->ptr, "min_x", sad->sa1->totrct.xmin);
3297     RNA_int_set(op->ptr, "min_y", sad->sa1->totrct.ymin);
3298     RNA_int_set(op->ptr, "max_x", sad->sa2->totrct.xmin);
3299     RNA_int_set(op->ptr, "max_y", sad->sa2->totrct.ymin);
3300   }
3301
3302   if (!area_join_init(C, op)) {
3303     return OPERATOR_CANCELLED;
3304   }
3305
3306   /* add temp handler */
3307   WM_event_add_modal_handler(C, op);
3308
3309   return OPERATOR_RUNNING_MODAL;
3310 }
3311
3312 static void area_join_cancel(bContext *C, wmOperator *op)
3313 {
3314   WM_event_add_notifier(C, NC_WINDOW, NULL);
3315
3316   area_join_exit(C, op);
3317 }
3318
3319 /* modal callback while selecting area (space) that will be removed */
3320 static int area_join_modal(bContext *C, wmOperator *op, const wmEvent *event)
3321 {
3322   bScreen *sc = CTX_wm_screen(C);
3323   wmWindow *win = CTX_wm_window(C);
3324   sAreaJoinData *jd = (sAreaJoinData *)op->customdata;
3325
3326   /* execute the events */
3327   switch (event->type) {
3328
3329     case MOUSEMOVE: {
3330       ScrArea *sa = BKE_screen_find_area_xy(sc, SPACE_TYPE_ANY, event->x, event->y);
3331       int dir = -1;
3332
3333       if (sa) {
3334         if (jd->sa1 != sa) {
3335           dir = area_getorientation(jd->sa1, sa);
3336           if (dir != -1) {
3337             jd->sa2 = sa;
3338           }
3339           else {
3340             /* we are not bordering on the previously selected area
3341              * we check if area has common border with the one marked for removal
3342              * in this case we can swap areas.
3343              */
3344             dir = area_getorientation(sa, jd->sa2);
3345             if (dir != -1) {
3346               jd->sa1 = jd->sa2;
3347               jd->sa2 = sa;
3348             }
3349             else {
3350               jd->sa2 = NULL;
3351             }
3352           }
3353           WM_event_add_notifier(C, NC_WINDOW, NULL);
3354         }
3355         else {
3356           /* we are back in the area previously selected for keeping
3357            * we swap the areas if possible to allow user to choose */
3358           if (jd->sa2 != NULL) {
3359             jd->sa1 = jd->sa2;
3360             jd->sa2 = sa;
3361             dir = area_getorientation(jd->sa1, jd->sa2);
3362             if (dir == -1) {
3363               printf("oops, didn't expect that!\n");
3364             }
3365           }
3366           else {
3367             dir = area_getorientation(jd->sa1, sa);
3368             if (dir != -1) {
3369               jd->sa2 = sa;
3370             }
3371           }
3372           WM_event_add_notifier(C, NC_WINDOW, NULL);
3373         }
3374       }
3375
3376       if (dir == 1) {
3377         WM_cursor_set(win, BC_N_ARROWCURSOR);
3378       }
3379       else if (dir == 3) {
3380         WM_cursor_set(win, BC_S_ARROWCURSOR);
3381       }
3382       else if (dir == 2) {
3383         WM_cursor_set(win, BC_E_ARROWCURSOR);
3384       }
3385       else if (dir == 0) {
3386         WM_cursor_set(win, BC_W_ARROWCURSOR);
3387       }
3388       else {
3389         WM_cursor_set(win, BC_STOPCURSOR);
3390       }
3391
3392       break;
3393     }
3394     case LEFTMOUSE:
3395       if (event->val == KM_RELEASE) {
3396         ED_area_tag_redraw(jd->sa1);
3397         ED_area_tag_redraw(jd->sa2);
3398
3399         area_join_apply(C, op);
3400         WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3401         area_join_exit(C, op);
3402         return OPERATOR_FINISHED;
3403       }
3404       break;
3405
3406     case RIGHTMOUSE:
3407     case ESCKEY:
3408       area_join_cancel(C, op);
3409       return OPERATOR_CANCELLED;
3410   }
3411
3412   return OPERATOR_RUNNING_MODAL;
3413 }
3414
3415 /* Operator for joining two areas (space types) */
3416 static void SCREEN_OT_area_join(wmOperatorType *ot)
3417 {
3418   /* identifiers */
3419   ot->name = "Join Area";
3420   ot->description = "Join selected areas into new window";
3421   ot->idname = "SCREEN_OT_area_join";
3422
3423   /* api callbacks */
3424   ot->exec = area_join_exec;
3425   ot->invoke = area_join_invoke;
3426   ot->modal = area_join_modal;
3427   ot->poll = screen_active_editable;
3428   ot->cancel = area_join_cancel;
3429
3430   /* flags */
3431   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
3432
3433   /* rna */
3434   RNA_def_int(ot->srna, "min_x", -100, INT_MIN, INT_MAX, "X 1", "", INT_MIN, INT_MAX);
3435   RNA_def_int(ot->srna, "min_y", -100, INT_MIN, INT_MAX, "Y 1", "", INT_MIN, INT_MAX);
3436   RNA_def_int(ot->srna, "max_x", -100, INT_MIN, INT_MAX, "X 2", "", INT_MIN, INT_MAX);
3437   RNA_def_int(ot->srna, "max_y", -100, INT_MIN, INT_MAX, "Y 2", "", INT_MIN, INT_MAX);
3438 }
3439
3440 /** \} */
3441
3442 /* -------------------------------------------------------------------- */
3443 /** \name Screen Area Options Operator
3444  * \{ */
3445
3446 static int screen_area_options_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3447 {
3448   const wmWindow *win = CTX_wm_window(C);
3449   const bScreen *sc = CTX_wm_screen(C);
3450   uiPopupMenu *pup;
3451   uiLayout *layout;
3452   PointerRNA ptr;
3453   ScrEdge *actedge;
3454   rcti window_rect;
3455
3456   WM_window_rect_calc(win, &window_rect);
3457   actedge = screen_geom_area_map_find_active_scredge(
3458       AREAMAP_FROM_SCREEN(sc), &window_rect, event->x, event->y);
3459
3460   if (actedge == NULL) {
3461     return OPERATOR_CANCELLED;
3462   }
3463
3464   pup = UI_popup_menu_begin(C, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
3465   layout = UI_popup_menu_layout(pup);
3466
3467   uiItemFullO(
3468       layout, "SCREEN_OT_area_split", NULL, ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &ptr);
3469   /* store initial mouse cursor position */
3470   RNA_int_set_array(&ptr, "cursor", &event->x);
3471
3472   uiItemFullO(layout, "SCREEN_OT_area_join", NULL, ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &ptr);
3473   /* mouse cursor on edge, '4' can fail on wide edges... */
3474   RNA_int_set(&ptr, "min_x", event->x + 4);
3475   RNA_int_set(&ptr, "min_y", event->y + 4);
3476   RNA_int_set(&ptr, "max_x", event->x - 4);
3477   RNA_int_set(&ptr, "max_y", event->y - 4);
3478
3479   UI_popup_menu_end(C, pup);
3480
3481   return OPERATOR_INTERFACE;
3482 }
3483
3484 static void SCREEN_OT_area_options(wmOperatorType *ot)
3485 {
3486   /* identifiers */
3487   ot->name = "Area Options";
3488   ot->description = "Operations for splitting and merging";
3489   ot->idname = "SCREEN_OT_area_options";
3490
3491   /* api callbacks */
3492   ot->invoke = screen_area_options_invoke;
3493
3494   ot->poll = ED_operator_screen_mainwinactive;
3495
3496   /* flags */
3497   ot->flag = OPTYPE_INTERNAL;
3498 }
3499
3500 /** \} */
3501
3502 /* -------------------------------------------------------------------- */
3503 /** \name Space Data Cleanup Operator
3504  * \{ */
3505
3506 static int spacedata_cleanup_exec(bContext *C, wmOperator *op)
3507 {
3508   Main *bmain = CTX_data_main(C);
3509   bScreen *screen;
3510   ScrArea *sa;
3511   int tot = 0;
3512
3513   for (screen = bmain->screens.first; screen; screen = screen->id.next) {
3514     for (sa = screen->areabase.first; sa; sa = sa->next) {
3515       if (sa->spacedata.first != sa->spacedata.last) {
3516         SpaceLink *sl = sa->spacedata.first;
3517
3518         BLI_remlink(&sa->spacedata, sl);
3519         tot += BLI_listbase_count(&sa->spacedata);
3520         BKE_spacedata_freelist(&sa->spacedata);
3521         BLI_addtail(&sa->spacedata, sl);
3522       }
3523     }
3524   }
3525   BKE_reportf(op->reports, RPT_INFO, "Removed amount of editors: %d", tot);
3526
3527   return OPERATOR_FINISHED;
3528 }
3529
3530 static void SCREEN_OT_spacedata_cleanup(wmOperatorType *ot)
3531 {
3532   /* identifiers */
3533   ot->name = "Clean-up Space-data";
3534   ot->description = "Remove unused settings for invisible editors";
3535   ot->idname = "SCREEN_OT_spacedata_cleanup";
3536
3537   /* api callbacks */
3538   ot->exec = spacedata_cleanup_exec;
3539   ot->poll = WM_operator_winactive;
3540 }
3541
3542 /** \} */
3543
3544 /* -------------------------------------------------------------------- */
3545 /** \name Repeat Last Operator
3546  * \{ */
3547
3548 static int repeat_last_exec(bContext *C, wmOperator *UNUSED(op))
3549 {
3550   wmWindowManager *wm = CTX_wm_manager(C);
3551   wmOperator *lastop = wm->operators.last;
3552
3553   /* Seek last registered operator */
3554   while (lastop) {
3555     if (lastop->type->flag & OPTYPE_REGISTER) {
3556       break;
3557     }
3558     else {
3559       lastop = lastop->prev;
3560     }
3561   }
3562
3563   if (lastop) {
3564     WM_operator_free_all_after(wm, lastop);
3565     WM_operator_repeat_last(C, lastop);
3566   }
3567
3568   return OPERATOR_CANCELLED;
3569 }
3570
3571 static void SCREEN_OT_repeat_last(wmOperatorType *ot)
3572 {
3573   /* identifiers */
3574   ot->name = "Repeat Last";
3575   ot->description = "Repeat last action";
3576   ot->idname = "SCREEN_OT_repeat_last";
3577
3578   /* api callbacks */
3579   ot->exec = repeat_last_exec;
3580
3581   ot->poll = ED_operator_screenactive;
3582 }
3583
3584 /** \} */
3585
3586 /* -------------------------------------------------------------------- */
3587 /** \name Repeat History Operator
3588  * \{ */
3589
3590 static int repeat_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
3591 {
3592   wmWindowManager *wm = CTX_wm_manager(C);
3593   wmOperator *lastop;
3594   uiPopupMenu *pup;
3595   uiLayout *layout;
3596   int items, i;
3597
3598   items = BLI_listbase_count(&wm->operators);
3599   if (items == 0) {
3600     return OPERATOR_CANCELLED;
3601   }
3602
3603   pup = UI_popup_menu_begin(C, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
3604   layout = UI_popup_menu_layout(pup);
3605
3606   for (i = items - 1, lastop = wm->operators.last; lastop; lastop = lastop->prev, i--) {
3607     if ((lastop->type->flag & OPTYPE_REGISTER) && WM_operator_repeat_check(C, lastop)) {
3608       uiItemIntO(layout,
3609                  WM_operatortype_name(lastop->type, lastop->ptr),
3610                  ICON_NONE,
3611                  op->type->idname,
3612                  "index",
3613                  i);
3614     }
3615   }
3616
3617   UI_popup_menu_end(C, pup);
3618
3619   return OPERATOR_INTERFACE;
3620 }
3621
3622 static int repeat_history_exec(bContext *C, wmOperator *op)
3623 {
3624   wmWindowManager *wm = CTX_wm_manager(C);
3625
3626   op = BLI_findlink(&wm->operators, RNA_int_get(op->ptr, "index"));
3627   if (op) {
3628     /* let's put it as last operator in list */
3629     BLI_remlink(&wm->operators, op);
3630     BLI_addtail(&wm->operators, op);
3631
3632     WM_operator_repeat(C, op);
3633   }
3634
3635   return OPERATOR_FINISHED;
3636 }
3637
3638 static void SCREEN_OT_repeat_history(wmOperatorType *ot)
3639 {
3640   /* identifiers */
3641   ot->name = "Repeat History";
3642   ot->description = "Display menu for previous actions performed";
3643   ot->idname = "SCREEN_OT_repeat_history";
3644
3645   /* api callbacks */
3646   ot->invoke = repeat_history_invoke;
3647   ot->exec = repeat_history_exec;
3648
3649   ot->poll = ED_operator_screenactive;
3650
3651   RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
3652 }
3653
3654 /** \} */
3655
3656 /* -------------------------------------------------------------------- */
3657 /** \name Redo Operator
3658  * \{ */
3659
3660 static int redo_last_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event))
3661 {
3662   wmOperator *lastop = WM_operator_last_redo(C);
3663
3664   if (lastop) {
3665     WM_operator_redo_popup(C, lastop);
3666   }
3667
3668   return OPERATOR_CANCELLED;
3669 }
3670
3671 static void SCREEN_OT_redo_last(wmOperatorType *ot)
3672 {
3673   /* identifiers */
3674   ot->name = "Redo Last";
3675   ot->description = "Display menu for last action performed";
3676   ot->idname = "SCREEN_OT_redo_last";
3677
3678   /* api callbacks */
3679   ot->invoke = redo_last_invoke;
3680
3681   ot->poll = ED_operator_screenactive;
3682 }
3683
3684 /** \} */
3685
3686 /* -------------------------------------------------------------------- */
3687 /** \name Region Quad-View Operator
3688  * \{ */
3689
3690 static void view3d_localview_update_rv3d(struct RegionView3D *rv3d)
3691 {
3692   if (rv3d->localvd) {
3693     rv3d->localvd->view = rv3d->view;
3694     rv3d->localvd->persp = rv3d->persp;
3695     copy_qt_qt(rv3d->localvd->viewquat, rv3d->viewquat);
3696   }
3697 }
3698
3699 static void region_quadview_init_rv3d(
3700     ScrArea *sa, ARegion *ar, const char viewlock, const char view, const char persp)
3701 {
3702   RegionView3D *rv3d = ar->regiondata;
3703
3704   if (persp == RV3D_CAMOB) {
3705     ED_view3d_lastview_store(rv3d);
3706   }
3707
3708   rv3d->viewlock = viewlock;
3709   rv3d->view = view;
3710   rv3d->persp = persp;
3711
3712   ED_view3d_lock(rv3d);
3713   view3d_localview_update_rv3d(rv3d);
3714   if ((viewlock & RV3D_BOXCLIP) && (persp == RV3D_ORTHO)) {
3715     ED_view3d_quadview_update(sa, ar, true);
3716   }
3717 }
3718
3719 /* insert a region in the area region list */
3720 static int region_quadview_exec(bContext *C, wmOperator *op)
3721 {
3722   ARegion *ar = CTX_wm_region(C);
3723
3724   /* some rules... */
3725   if (ar->regiontype != RGN_TYPE_WINDOW) {
3726     BKE_report(op->reports, RPT_ERROR, "Only window region can be 4-splitted");
3727   }
3728   else if (ar->alignment == RGN_ALIGN_QSPLIT) {
3729     /* Exit quad-view */
3730     ScrArea *sa = CTX_wm_area(C);
3731     ARegion *arn;
3732
3733     /* keep current region */
3734     ar->alignment = 0;
3735
3736     if (sa->spacetype == SPACE_VIEW3D) {
3737       ARegion *ar_iter;
3738       RegionView3D *rv3d = ar->regiondata;
3739
3740       /* if this is a locked view, use settings from 'User' view */
3741       if (rv3d->viewlock) {
3742         View3D *v3d_user;
3743         ARegion *ar_user;
3744
3745         if (ED_view3d_context_user_region(C, &v3d_user, &ar_user)) {
3746           if (ar != ar_user) {
3747             SWAP(void *, ar->regiondata, ar_user->regiondata);
3748             rv3d = ar->regiondata;
3749           }
3750         }
3751       }
3752
3753       rv3d->viewlock_quad = RV3D_VIEWLOCK_INIT;
3754       rv3d->viewlock = 0;
3755       rv3d->rflag &= ~RV3D_CLIPPING;
3756
3757       /* accumulate locks, incase they're mixed */
3758       for (ar_iter = sa->regionbase.first; ar_iter; ar_iter = ar_iter->next) {
3759         if (ar_iter->regiontype == RGN_TYPE_WINDOW) {
3760           RegionView3D *rv3d_iter = ar_iter->regiondata;
3761           rv3d->viewlock_quad |= rv3d_iter->viewlock;
3762         }
3763       }
3764     }
3765
3766     for (ar = sa->regionbase.first; ar; ar = arn) {
3767       arn = ar->next;
3768       if (ar->alignment == RGN_ALIGN_QSPLIT) {
3769         ED_region_exit(C, ar);
3770         BKE_area_region_free(sa->type, ar);
3771         BLI_remlink(&sa->regionbase, ar);
3772         MEM_freeN(ar);
3773       }
3774     }
3775     ED_area_tag_redraw(sa);
3776     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
3777   }
3778   else if (ar->next) {
3779     BKE_report(op->reports, RPT_ERROR, "Only last region can be 4-splitted");
3780   }
3781   else {
3782     /* Enter quad-view */
3783     ScrArea *sa = CTX_wm_area(C);
3784     ARegion *newar;
3785     int count;
3786
3787     ar->alignment = RGN_ALIGN_QSPLIT;
3788
3789     for (count = 0; count < 3; count++) {
3790       newar = BKE_area_region_copy(sa->type, ar);
3791       BLI_addtail(&sa->regionbase, newar);
3792     }
3793
3794     /* lock views and set them */
3795     if (sa->spacetype == SPACE_VIEW3D) {
3796       View3D *v3d = sa->spacedata.first;
3797       int index_qsplit = 0;
3798
3799       /* run ED_view3d_lock() so the correct 'rv3d->viewquat' is set,
3800        * otherwise when restoring rv3d->localvd the 'viewquat' won't
3801        * match the 'view', set on entering localview See: [#26315],
3802        *
3803        * We could avoid manipulating rv3d->localvd here if exiting
3804        * localview with a 4-split would assign these view l