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