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