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