99c46c38bfc9b419df9de5dd766c4677574b833c
[blender.git] / source / blender / editors / mask / mask_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) 2012 Blender Foundation.
19  * All rights reserved.
20  *
21  *
22  * Contributor(s): Blender Foundation,
23  *                 Sergey Sharybin
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/mask/mask_ops.c
29  *  \ingroup edmask
30  */
31
32 #include "MEM_guardedalloc.h"
33
34 #include "BLI_listbase.h"
35 #include "BLI_math.h"
36
37 #include "BKE_context.h"
38 #include "BKE_depsgraph.h"
39 #include "BKE_mask.h"
40
41 #include "DNA_scene_types.h"
42 #include "DNA_mask_types.h"
43 #include "DNA_object_types.h"  /* SELECT */
44
45 #include "WM_api.h"
46 #include "WM_types.h"
47
48 #include "ED_clip.h"
49 #include "ED_image.h"
50 #include "ED_keyframing.h"
51 #include "ED_mask.h"
52 #include "ED_screen.h"
53
54 #include "RNA_access.h"
55 #include "RNA_define.h"
56
57 #include "mask_intern.h"  /* own include */
58
59 /******************** utility functions *********************/
60
61 MaskSplinePoint *ED_mask_point_find_nearest(const bContext *C, Mask *mask, float normal_co[2], int threshold,
62                                             MaskLayer **masklay_r, MaskSpline **spline_r, int *is_handle_r,
63                                             float *score)
64 {
65         MaskLayer *masklay;
66         MaskLayer *point_masklay = NULL;
67         MaskSpline *point_spline = NULL;
68         MaskSplinePoint *point = NULL;
69         float co[2], aspx, aspy;
70         float len = FLT_MAX, scalex, scaley;
71         int is_handle = FALSE, width, height;
72
73         ED_mask_size(C, &width, &height);
74         ED_mask_aspect(C, &aspx, &aspy);
75         ED_mask_pixelspace_factor(C, &scalex, &scaley);
76
77         co[0] = normal_co[0] * scalex;
78         co[1] = normal_co[1] * scaley;
79
80         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
81                 MaskSpline *spline;
82
83                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
84                         continue;
85                 }
86
87                 for (spline = masklay->splines.first; spline; spline = spline->next) {
88                         MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
89
90                         int i;
91
92                         for (i = 0; i < spline->tot_point; i++) {
93                                 MaskSplinePoint *cur_point = &spline->points[i];
94                                 MaskSplinePoint *cur_point_deform = &points_array[i];
95                                 float cur_len, vec[2], handle[2];
96
97                                 vec[0] = cur_point_deform->bezt.vec[1][0] * scalex;
98                                 vec[1] = cur_point_deform->bezt.vec[1][1] * scaley;
99
100                                 if (BKE_mask_point_has_handle(cur_point)) {
101                                         BKE_mask_point_handle(cur_point_deform, handle);
102                                         handle[0] *= scalex;
103                                         handle[1] *= scaley;
104
105                                         cur_len = len_v2v2(co, handle);
106
107                                         if (cur_len < len) {
108                                                 point_masklay = masklay;
109                                                 point_spline = spline;
110                                                 point = cur_point;
111                                                 len = cur_len;
112                                                 is_handle = TRUE;
113                                         }
114                                 }
115
116                                 cur_len = len_v2v2(co, vec);
117
118                                 if (cur_len < len) {
119                                         point_spline = spline;
120                                         point_masklay = masklay;
121                                         point = cur_point;
122                                         len = cur_len;
123                                         is_handle = FALSE;
124                                 }
125                         }
126                 }
127         }
128
129         if (len < threshold) {
130                 if (masklay_r)
131                         *masklay_r = point_masklay;
132
133                 if (spline_r)
134                         *spline_r = point_spline;
135
136                 if (is_handle_r)
137                         *is_handle_r = is_handle;
138
139                 if (score)
140                         *score = len;
141
142                 return point;
143         }
144
145         if (masklay_r)
146                 *masklay_r = NULL;
147
148         if (spline_r)
149                 *spline_r = NULL;
150
151         if (is_handle_r)
152                 *is_handle_r = FALSE;
153
154         return NULL;
155 }
156
157 int ED_mask_feather_find_nearest(const bContext *C, Mask *mask, float normal_co[2], int threshold,
158                                  MaskLayer **masklay_r, MaskSpline **spline_r, MaskSplinePoint **point_r,
159                                  MaskSplinePointUW **uw_r, float *score)
160 {
161         MaskLayer *masklay, *point_masklay = NULL;
162         MaskSpline *point_spline = NULL;
163         MaskSplinePoint *point = NULL;
164         MaskSplinePointUW *uw = NULL;
165         float len = FLT_MAX, co[2];
166         float scalex, scaley, aspx, aspy;
167         int width, height;
168
169         ED_mask_size(C, &width, &height);
170         ED_mask_aspect(C, &aspx, &aspy);
171         ED_mask_pixelspace_factor(C, &scalex, &scaley);
172
173         co[0] = normal_co[0] * scalex;
174         co[1] = normal_co[1] * scaley;
175
176         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
177                 MaskSpline *spline;
178
179                 for (spline = masklay->splines.first; spline; spline = spline->next) {
180                         //MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
181
182                         int i, tot_feather_point;
183                         float (*feather_points)[2], (*fp)[2];
184
185                         if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
186                                 continue;
187                         }
188
189                         feather_points = fp = BKE_mask_spline_feather_points(spline, &tot_feather_point);
190
191                         for (i = 0; i < spline->tot_point; i++) {
192                                 int j;
193                                 MaskSplinePoint *cur_point = &spline->points[i];
194
195                                 for (j = 0; j < cur_point->tot_uw + 1; j++) {
196                                         float cur_len, vec[2];
197
198                                         vec[0] = (*fp)[0] * scalex;
199                                         vec[1] = (*fp)[1] * scaley;
200
201                                         cur_len = len_v2v2(vec, co);
202
203                                         if (point == NULL || cur_len < len) {
204                                                 if (j == 0)
205                                                         uw = NULL;
206                                                 else
207                                                         uw = &cur_point->uw[j - 1];
208
209                                                 point_masklay = masklay;
210                                                 point_spline = spline;
211                                                 point = cur_point;
212                                                 len = cur_len;
213                                         }
214
215                                         fp++;
216                                 }
217                         }
218
219                         MEM_freeN(feather_points);
220                 }
221         }
222
223         if (len < threshold) {
224                 if (masklay_r)
225                         *masklay_r = point_masklay;
226
227                 if (spline_r)
228                         *spline_r = point_spline;
229
230                 if (point_r)
231                         *point_r = point;
232
233                 if (uw_r)
234                         *uw_r = uw;
235
236                 if (score)
237                         *score = len;
238
239                 return TRUE;
240         }
241
242         if (masklay_r)
243                 *masklay_r = NULL;
244
245         if (spline_r)
246                 *spline_r = NULL;
247
248         if (point_r)
249                 *point_r = NULL;
250
251         return FALSE;
252 }
253
254
255 /******************** create new mask *********************/
256
257 static int mask_new_exec(bContext *C, wmOperator *op)
258 {
259         ScrArea *sa = CTX_wm_area(C);
260         Mask *mask;
261         char name[MAX_ID_NAME - 2];
262
263         RNA_string_get(op->ptr, "name", name);
264
265         mask = BKE_mask_new(name);
266
267         if (sa && sa->spacedata.first) {
268                 switch (sa->spacetype) {
269                         case SPACE_CLIP:
270                         {
271                                 SpaceClip *sc = sa->spacedata.first;
272                                 ED_space_clip_set_mask(C, sc, mask);
273                                 break;
274                         }
275                         case SPACE_SEQ:
276                         {
277                                 /* do nothing */
278                                 break;
279                         }
280                         case SPACE_IMAGE:
281                         {
282                                 SpaceImage *sima = sa->spacedata.first;
283                                 ED_space_image_set_mask(C, sima, mask);
284                                 break;
285                         }
286                 }
287         }
288
289         return OPERATOR_FINISHED;
290 }
291
292 void MASK_OT_new(wmOperatorType *ot)
293 {
294         /* identifiers */
295         ot->name = "New Mask";
296         ot->description = "Create new mask";
297         ot->idname = "MASK_OT_new";
298
299         /* flags */
300         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
301
302         /* api callbacks */
303         ot->exec = mask_new_exec;
304         ot->poll = ED_operator_mask;
305
306         /* properties */
307         RNA_def_string(ot->srna, "name", "", MAX_ID_NAME - 2, "Name", "Name of new mask");
308 }
309
310 /******************** create new masklay *********************/
311
312 static int masklay_new_exec(bContext *C, wmOperator *op)
313 {
314         Mask *mask = CTX_data_edit_mask(C);
315         char name[MAX_ID_NAME - 2];
316
317         RNA_string_get(op->ptr, "name", name);
318
319         BKE_mask_layer_new(mask, name);
320         mask->masklay_act = mask->masklay_tot - 1;
321
322         WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask);
323
324         return OPERATOR_FINISHED;
325 }
326
327 void MASK_OT_layer_new(wmOperatorType *ot)
328 {
329         /* identifiers */
330         ot->name = "Add Mask Layer";
331         ot->description = "Add new mask layer for masking";
332         ot->idname = "MASK_OT_layer_new";
333
334         /* api callbacks */
335         ot->exec = masklay_new_exec;
336         ot->poll = ED_maskedit_poll;
337
338         /* flags */
339         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
340
341         /* properties */
342         RNA_def_string(ot->srna, "name", "", MAX_ID_NAME - 2, "Name", "Name of new mask layer");
343 }
344
345 /******************** remove mask layer *********************/
346
347 static int masklay_remove_exec(bContext *C, wmOperator *UNUSED(op))
348 {
349         Mask *mask = CTX_data_edit_mask(C);
350         MaskLayer *masklay = BKE_mask_layer_active(mask);
351
352         if (masklay) {
353                 BKE_mask_layer_remove(mask, masklay);
354
355                 WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask);
356         }
357
358         return OPERATOR_FINISHED;
359 }
360
361 void MASK_OT_layer_remove(wmOperatorType *ot)
362 {
363         /* identifiers */
364         ot->name = "Remove Mask Layer";
365         ot->description = "Remove mask layer";
366         ot->idname = "MASK_OT_layer_remove";
367
368         /* api callbacks */
369         ot->exec = masklay_remove_exec;
370         ot->poll = ED_maskedit_poll;
371
372         /* flags */
373         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
374 }
375
376 /******************** slide *********************/
377
378 enum {
379         SLIDE_ACTION_NONE    = 0,
380         SLIDE_ACTION_POINT   = 1,
381         SLIDE_ACTION_HANDLE  = 2,
382         SLIDE_ACTION_FEATHER = 3
383 };
384
385 typedef struct SlidePointData {
386         int action;
387
388         float co[2];
389         float vec[3][3];
390
391         Mask *mask;
392         MaskLayer *masklay;
393         MaskSpline *spline, *orig_spline;
394         MaskSplinePoint *point;
395         MaskSplinePointUW *uw;
396         float handle[2], no[2], feather[2];
397         int width, height;
398         float weight, weight_scalar;
399
400         short curvature_only, accurate;
401         short initial_feather, overall_feather;
402 } SlidePointData;
403
404 static int slide_point_check_initial_feather(MaskSpline *spline)
405 {
406         int i;
407
408         for (i = 0; i < spline->tot_point; i++) {
409                 MaskSplinePoint *point = &spline->points[i];
410
411                 if (point->bezt.weight != 0.0f)
412                         return FALSE;
413
414                 /* comment for now. if all bezt weights are zero - this is as good-as initial */
415 #if 0
416                 int j;
417                 for (j = 0; j < point->tot_uw; j++) {
418                         if (point->uw[j].w != 0.0f)
419                                 return FALSE;
420                 }
421 #endif
422         }
423
424         return TRUE;
425 }
426
427 static void *slide_point_customdata(bContext *C, wmOperator *op, wmEvent *event)
428 {
429         Mask *mask = CTX_data_edit_mask(C);
430         SlidePointData *customdata = NULL;
431         MaskLayer *masklay, *cv_masklay, *feather_masklay;
432         MaskSpline *spline, *cv_spline, *feather_spline;
433         MaskSplinePoint *point, *cv_point, *feather_point;
434         MaskSplinePointUW *uw = NULL;
435         int is_handle = FALSE, width, height, action = SLIDE_ACTION_NONE;
436         int slide_feather = RNA_boolean_get(op->ptr, "slide_feather");
437         float co[2], cv_score, feather_score;
438         const float threshold = 19;
439
440         ED_mask_mouse_pos(C, event, co);
441         ED_mask_size(C, &width, &height);
442
443         cv_point = ED_mask_point_find_nearest(C, mask, co, threshold, &cv_masklay, &cv_spline, &is_handle, &cv_score);
444
445         if (ED_mask_feather_find_nearest(C, mask, co, threshold, &feather_masklay, &feather_spline, &feather_point, &uw, &feather_score)) {
446                 if (slide_feather || !cv_point || feather_score < cv_score) {
447                         action = SLIDE_ACTION_FEATHER;
448
449                         masklay = feather_masklay;
450                         spline = feather_spline;
451                         point = feather_point;
452                 }
453         }
454
455         if (cv_point && action == SLIDE_ACTION_NONE) {
456                 if (is_handle)
457                         action = SLIDE_ACTION_HANDLE;
458                 else
459                         action = SLIDE_ACTION_POINT;
460
461                 masklay = cv_masklay;
462                 spline = cv_spline;
463                 point = cv_point;
464         }
465
466         if (action != SLIDE_ACTION_NONE) {
467                 customdata = MEM_callocN(sizeof(SlidePointData), "mask slide point data");
468
469                 customdata->mask = mask;
470                 customdata->masklay = masklay;
471                 customdata->spline = spline;
472                 customdata->point = point;
473                 customdata->width = width;
474                 customdata->height = height;
475                 customdata->action = action;
476                 customdata->uw = uw;
477
478                 if (uw) {
479                         float co[2];
480                         float weight_scalar = BKE_mask_point_weight_scalar(spline, point, uw->u);
481
482                         customdata->weight = uw->w;
483                         customdata->weight_scalar = weight_scalar;
484                         BKE_mask_point_segment_co(spline, point, uw->u, co);
485                         BKE_mask_point_normal(spline, point, uw->u, customdata->no);
486
487                         madd_v2_v2v2fl(customdata->feather, co, customdata->no, uw->w * weight_scalar);
488                 }
489                 else {
490                         BezTriple *bezt = &point->bezt;
491
492                         customdata->weight = bezt->weight;
493                         customdata->weight_scalar = 1.0f;
494                         BKE_mask_point_normal(spline, point, 0.0f, customdata->no);
495
496                         madd_v2_v2v2fl(customdata->feather, bezt->vec[1], customdata->no, bezt->weight);
497                 }
498
499                 if (customdata->action == SLIDE_ACTION_FEATHER)
500                         customdata->initial_feather = slide_point_check_initial_feather(spline);
501
502                 copy_m3_m3(customdata->vec, point->bezt.vec);
503                 if (BKE_mask_point_has_handle(point))
504                         BKE_mask_point_handle(point, customdata->handle);
505                 ED_mask_mouse_pos(C, event, customdata->co);
506         }
507
508         return customdata;
509 }
510
511 static int slide_point_invoke(bContext *C, wmOperator *op, wmEvent *event)
512 {
513         SlidePointData *slidedata = slide_point_customdata(C, op, event);
514
515         if (slidedata) {
516                 Mask *mask = CTX_data_edit_mask(C);
517
518                 op->customdata = slidedata;
519
520                 WM_event_add_modal_handler(C, op);
521
522                 if (slidedata->uw) {
523                         if ((slidedata->uw->flag & SELECT) == 0) {
524                                 ED_mask_select_toggle_all(mask, SEL_DESELECT);
525
526                                 slidedata->uw->flag |= SELECT;
527
528                                 ED_mask_select_flush_all(mask);
529                         }
530                 }
531                 else if (!MASKPOINT_ISSEL_ANY(slidedata->point)) {
532                         ED_mask_select_toggle_all(mask, SEL_DESELECT);
533
534                         BKE_mask_point_select_set(slidedata->point, TRUE);
535
536                         ED_mask_select_flush_all(mask);
537                 }
538
539                 slidedata->masklay->act_spline = slidedata->spline;
540                 slidedata->masklay->act_point = slidedata->point;
541
542                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
543
544                 return OPERATOR_RUNNING_MODAL;
545         }
546
547         return OPERATOR_PASS_THROUGH;
548 }
549
550 static void slide_point_delta_all_feather(SlidePointData *data, float delta)
551 {
552         int i;
553
554         for (i = 0; i < data->spline->tot_point; i++) {
555                 MaskSplinePoint *point = &data->spline->points[i];
556                 MaskSplinePoint *orig_point = &data->orig_spline->points[i];
557
558                 point->bezt.weight = orig_point->bezt.weight + delta;
559                 if (point->bezt.weight < 0.0f)
560                         point->bezt.weight = 0.0f;
561
562                 /* not needed anymore */
563 #if 0
564                 int j;
565                 for (j = 0; j < point->tot_uw; j++) {
566                         point->uw[j].w = orig_point->uw[j].w + delta;
567                         if (point->uw[j].w < 0.0f)
568                                 point->uw[j].w = 0.0f;
569                 }
570 #endif
571         }
572 }
573
574 static void slide_point_restore_spline(SlidePointData *data)
575 {
576         int i;
577
578         for (i = 0; i < data->spline->tot_point; i++) {
579                 MaskSplinePoint *point = &data->spline->points[i];
580                 MaskSplinePoint *orig_point = &data->orig_spline->points[i];
581                 int j;
582
583                 point->bezt = orig_point->bezt;
584
585                 for (j = 0; j < point->tot_uw; j++)
586                         point->uw[j] = orig_point->uw[j];
587         }
588 }
589
590 static void cancel_slide_point(SlidePointData *data)
591 {
592         /* cancel sliding */
593
594         if (data->orig_spline) {
595                 slide_point_restore_spline(data);
596         }
597         else {
598                 if (data->action == SLIDE_ACTION_FEATHER) {
599                         if (data->uw)
600                                 data->uw->w = data->weight;
601                         else
602                                 data->point->bezt.weight = data->weight;
603                 }
604                 else {
605                         copy_m3_m3(data->point->bezt.vec, data->vec);
606                 }
607         }
608 }
609
610 static void free_slide_point_data(SlidePointData *data)
611 {
612         if (data->orig_spline)
613                 BKE_mask_spline_free(data->orig_spline);
614
615         MEM_freeN(data);
616 }
617
618 static int slide_point_modal(bContext *C, wmOperator *op, wmEvent *event)
619 {
620         SlidePointData *data = (SlidePointData *)op->customdata;
621         BezTriple *bezt = &data->point->bezt;
622         float co[2], dco[2];
623
624         switch (event->type) {
625                 case LEFTCTRLKEY:
626                 case RIGHTCTRLKEY:
627                 case LEFTSHIFTKEY:
628                 case RIGHTSHIFTKEY:
629                         if (ELEM(event->type, LEFTCTRLKEY, RIGHTCTRLKEY)) {
630                                 if (data->action == SLIDE_ACTION_FEATHER)
631                                         data->overall_feather = event->val == KM_PRESS;
632                                 else
633                                         data->curvature_only = event->val == KM_PRESS;
634                         }
635
636                         if (ELEM(event->type, LEFTSHIFTKEY, RIGHTSHIFTKEY))
637                                 data->accurate = event->val == KM_PRESS;
638
639                 /* no break! update CV position */
640
641                 case MOUSEMOVE:
642                         ED_mask_mouse_pos(C, event, co);
643                         sub_v2_v2v2(dco, co, data->co);
644
645                         if (data->action == SLIDE_ACTION_HANDLE) {
646                                 float delta[2], offco[2];
647
648                                 sub_v2_v2v2(delta, data->handle, data->co);
649
650                                 sub_v2_v2v2(offco, co, data->co);
651                                 if (data->accurate)
652                                         mul_v2_fl(offco, 0.2f);
653                                 add_v2_v2(offco, data->co);
654                                 add_v2_v2(offco, delta);
655
656                                 BKE_mask_point_set_handle(data->point, offco, data->curvature_only, data->handle, data->vec);
657                         }
658                         else if (data->action == SLIDE_ACTION_POINT) {
659                                 float delta[2];
660
661                                 copy_v2_v2(delta, dco);
662                                 if (data->accurate)
663                                         mul_v2_fl(delta, 0.2f);
664
665                                 add_v2_v2v2(bezt->vec[0], data->vec[0], delta);
666                                 add_v2_v2v2(bezt->vec[1], data->vec[1], delta);
667                                 add_v2_v2v2(bezt->vec[2], data->vec[2], delta);
668                         }
669                         else if (data->action == SLIDE_ACTION_FEATHER) {
670                                 float vec[2], no[2], p[2], c[2], w, offco[2];
671                                 float *weight = NULL;
672                                 float weight_scalar = 1.0f;
673                                 int overall_feather = data->overall_feather || data->initial_feather;
674
675                                 add_v2_v2v2(offco, data->feather, dco);
676
677                                 if (data->uw) {
678                                         /* project on both sides and find the closest one,
679                                          * prevents flickering when projecting onto both sides can happen */
680                                         const float u_pos = BKE_mask_spline_project_co(data->spline, data->point,
681                                                                                        data->uw->u, offco, MASK_PROJ_NEG);
682                                         const float u_neg = BKE_mask_spline_project_co(data->spline, data->point,
683                                                                                        data->uw->u, offco, MASK_PROJ_POS);
684                                         float dist_pos = FLT_MAX;
685                                         float dist_neg = FLT_MAX;
686                                         float co_pos[2];
687                                         float co_neg[2];
688                                         float u;
689
690                                         if (u_pos > 0.0f && u_pos < 1.0f) {
691                                                 BKE_mask_point_segment_co(data->spline, data->point, u_pos, co_pos);
692                                                 dist_pos = len_squared_v2v2(offco, co_pos);
693                                         }
694
695                                         if (u_neg > 0.0f && u_neg < 1.0f) {
696                                                 BKE_mask_point_segment_co(data->spline, data->point, u_neg, co_neg);
697                                                 dist_neg = len_squared_v2v2(offco, co_neg);
698                                         }
699
700                                         u = dist_pos < dist_neg ? u_pos : u_neg;
701
702                                         if (u > 0.0f && u < 1.0f) {
703                                                 data->uw->u = u;
704
705                                                 data->uw = BKE_mask_point_sort_uw(data->point, data->uw);
706                                                 weight = &data->uw->w;
707                                                 weight_scalar = BKE_mask_point_weight_scalar(data->spline, data->point, u);
708                                                 if (weight_scalar != 0.0f) {
709                                                         weight_scalar = 1.0f / weight_scalar;
710                                                 }
711
712                                                 BKE_mask_point_normal(data->spline, data->point, data->uw->u, no);
713                                                 BKE_mask_point_segment_co(data->spline, data->point, data->uw->u, p);
714                                         }
715                                 }
716                                 else {
717                                         weight = &bezt->weight;
718                                         /* weight_scalar = 1.0f; keep as is */
719                                         copy_v2_v2(no, data->no);
720                                         copy_v2_v2(p, bezt->vec[1]);
721                                 }
722
723                                 if (weight) {
724                                         sub_v2_v2v2(c, offco, p);
725                                         project_v2_v2v2(vec, c, no);
726
727                                         w = len_v2(vec);
728
729                                         if (overall_feather) {
730                                                 float delta;
731
732                                                 if (dot_v2v2(no, vec) <= 0.0f)
733                                                         w = -w;
734
735                                                 delta = w - data->weight * data->weight_scalar;
736
737                                                 if (data->orig_spline == NULL) {
738                                                         /* restore weight for currently sliding point, so orig_spline would be created
739                                                          * with original weights used
740                                                          */
741                                                         *weight = data->weight;
742
743                                                         data->orig_spline = BKE_mask_spline_copy(data->spline);
744                                                 }
745
746                                                 slide_point_delta_all_feather(data, delta);
747                                         }
748                                         else {
749                                                 if (dot_v2v2(no, vec) <= 0.0f)
750                                                         w = 0.0f;
751
752                                                 if (data->orig_spline) {
753                                                         /* restore possible overall feather changes */
754                                                         slide_point_restore_spline(data);
755
756                                                         BKE_mask_spline_free(data->orig_spline);
757                                                         data->orig_spline = NULL;
758                                                 }
759
760                                                 if (weight_scalar != 0.0f) {
761                                                         *weight = w * weight_scalar;
762                                                 }
763                                         }
764                                 }
765                         }
766
767                         WM_event_add_notifier(C, NC_MASK | NA_EDITED, data->mask);
768                         DAG_id_tag_update(&data->mask->id, 0);
769
770                         break;
771
772                 case LEFTMOUSE:
773                         if (event->val == KM_RELEASE) {
774                                 Scene *scene = CTX_data_scene(C);
775
776                                 /* dont key sliding feather uw's */
777                                 if ((data->action == SLIDE_ACTION_FEATHER && data->uw) == FALSE) {
778                                         if (IS_AUTOKEY_ON(scene)) {
779                                                 ED_mask_layer_shape_auto_key(data->masklay, CFRA);
780                                         }
781                                 }
782
783                                 WM_event_add_notifier(C, NC_MASK | NA_EDITED, data->mask);
784                                 DAG_id_tag_update(&data->mask->id, 0);
785
786                                 free_slide_point_data(op->customdata); /* keep this last! */
787                                 return OPERATOR_FINISHED;
788                         }
789
790                         break;
791
792                 case ESCKEY:
793                         cancel_slide_point(op->customdata);
794
795                         WM_event_add_notifier(C, NC_MASK | NA_EDITED, data->mask);
796                         DAG_id_tag_update(&data->mask->id, 0);
797
798                         free_slide_point_data(op->customdata); /* keep this last! */
799                         return OPERATOR_CANCELLED;
800         }
801
802         return OPERATOR_RUNNING_MODAL;
803 }
804
805 void MASK_OT_slide_point(wmOperatorType *ot)
806 {
807         /* identifiers */
808         ot->name = "Slide Point";
809         ot->description = "Slide control points";
810         ot->idname = "MASK_OT_slide_point";
811
812         /* api callbacks */
813         ot->invoke = slide_point_invoke;
814         ot->modal = slide_point_modal;
815         ot->poll = ED_maskedit_mask_poll;
816
817         /* flags */
818         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
819
820         RNA_def_boolean(ot->srna, "slide_feather", 0, "Slide Feather", "First try to slide feather instead of vertex");
821 }
822
823 /******************** toggle cyclic *********************/
824
825 static int cyclic_toggle_exec(bContext *C, wmOperator *UNUSED(op))
826 {
827         Mask *mask = CTX_data_edit_mask(C);
828         MaskLayer *masklay;
829
830         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
831                 MaskSpline *spline;
832
833                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
834                         continue;
835                 }
836
837                 for (spline = masklay->splines.first; spline; spline = spline->next) {
838                         if (ED_mask_spline_select_check(spline)) {
839                                 spline->flag ^= MASK_SPLINE_CYCLIC;
840                         }
841                 }
842         }
843
844         WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask);
845
846         return OPERATOR_FINISHED;
847 }
848
849 void MASK_OT_cyclic_toggle(wmOperatorType *ot)
850 {
851         /* identifiers */
852         ot->name = "Toggle Cyclic";
853         ot->description = "Toggle cyclic for selected splines";
854         ot->idname = "MASK_OT_cyclic_toggle";
855
856         /* api callbacks */
857         ot->exec = cyclic_toggle_exec;
858         ot->poll = ED_maskedit_mask_poll;
859
860         /* flags */
861         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
862 }
863
864 /******************** delete *********************/
865
866 static void delete_feather_points(MaskSplinePoint *point)
867 {
868         int i, count = 0;
869
870         if (!point->tot_uw)
871                 return;
872
873         for (i = 0; i < point->tot_uw; i++) {
874                 if ((point->uw[i].flag & SELECT) == 0)
875                         count++;
876         }
877
878         if (count == 0) {
879                 MEM_freeN(point->uw);
880                 point->uw = NULL;
881                 point->tot_uw = 0;
882         }
883         else {
884                 MaskSplinePointUW *new_uw;
885                 int j = 0;
886
887                 new_uw = MEM_callocN(count * sizeof(MaskSplinePointUW), "new mask uw points");
888
889                 for (i = 0; i < point->tot_uw; i++) {
890                         if ((point->uw[i].flag & SELECT) == 0) {
891                                 new_uw[j++] = point->uw[i];
892                         }
893                 }
894
895                 MEM_freeN(point->uw);
896
897                 point->uw = new_uw;
898                 point->tot_uw = count;
899         }
900 }
901
902 static int delete_exec(bContext *C, wmOperator *UNUSED(op))
903 {
904         Mask *mask = CTX_data_edit_mask(C);
905         MaskLayer *masklay;
906
907         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
908                 MaskSpline *spline;
909                 int mask_layer_shape_ofs = 0;
910
911                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
912                         continue;
913                 }
914
915                 spline = masklay->splines.first;
916
917                 while (spline) {
918                         const int tot_point_orig = spline->tot_point;
919                         int i, count = 0;
920                         MaskSpline *next_spline = spline->next;
921
922                         /* count unselected points */
923                         for (i = 0; i < spline->tot_point; i++) {
924                                 MaskSplinePoint *point = &spline->points[i];
925
926                                 if (!MASKPOINT_ISSEL_ANY(point))
927                                         count++;
928                         }
929
930                         if (count == 0) {
931
932                                 /* delete the whole spline */
933                                 BLI_remlink(&masklay->splines, spline);
934                                 BKE_mask_spline_free(spline);
935
936                                 if (spline == masklay->act_spline) {
937                                         masklay->act_spline = NULL;
938                                         masklay->act_point = NULL;
939                                 }
940
941                                 BKE_mask_layer_shape_changed_remove(masklay, mask_layer_shape_ofs, tot_point_orig);
942                         }
943                         else {
944                                 MaskSplinePoint *new_points;
945                                 int j;
946
947                                 new_points = MEM_callocN(count * sizeof(MaskSplinePoint), "deleteMaskPoints");
948
949                                 for (i = 0, j = 0; i < tot_point_orig; i++) {
950                                         MaskSplinePoint *point = &spline->points[i];
951
952                                         if (!MASKPOINT_ISSEL_ANY(point)) {
953                                                 if (point == masklay->act_point)
954                                                         masklay->act_point = &new_points[j];
955
956                                                 delete_feather_points(point);
957
958                                                 new_points[j] = *point;
959                                                 j++;
960                                         }
961                                         else {
962                                                 if (point == masklay->act_point)
963                                                         masklay->act_point = NULL;
964
965                                                 BKE_mask_point_free(point);
966                                                 spline->tot_point--;
967
968                                                 BKE_mask_layer_shape_changed_remove(masklay, mask_layer_shape_ofs + j, 1);
969                                         }
970                                 }
971
972                                 mask_layer_shape_ofs += spline->tot_point;
973
974                                 MEM_freeN(spline->points);
975                                 spline->points = new_points;
976
977                                 ED_mask_select_flush_all(mask);
978                         }
979
980                         spline = next_spline;
981                 }
982
983                 /* not essential but confuses users when there are keys with no data!
984                  * assume if they delete all data from the layer they also dont care about keys */
985                 if (masklay->splines.first == NULL) {
986                         BKE_mask_layer_free_shapes(masklay);
987                 }
988         }
989
990         /* TODO: only update edited splines */
991         BKE_mask_update_display(mask, CTX_data_scene(C)->r.cfra);
992
993         WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask);
994
995         return OPERATOR_FINISHED;
996 }
997
998 void MASK_OT_delete(wmOperatorType *ot)
999 {
1000         /* identifiers */
1001         ot->name = "Delete";
1002         ot->description = "Delete selected control points or splines";
1003         ot->idname = "MASK_OT_delete";
1004
1005         /* api callbacks */
1006         ot->invoke = WM_operator_confirm;
1007         ot->exec = delete_exec;
1008         ot->poll = ED_maskedit_mask_poll;
1009
1010         /* flags */
1011         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1012 }
1013
1014 /* *** switch direction *** */
1015 static int mask_switch_direction_exec(bContext *C, wmOperator *UNUSED(op))
1016 {
1017         Scene *scene = CTX_data_scene(C);
1018         Mask *mask = CTX_data_edit_mask(C);
1019         MaskLayer *masklay;
1020
1021         int change = FALSE;
1022
1023         /* do actual selection */
1024         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
1025                 MaskSpline *spline;
1026                 int change_layer = FALSE;
1027
1028                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
1029                         continue;
1030                 }
1031
1032                 for (spline = masklay->splines.first; spline; spline = spline->next) {
1033                         if (ED_mask_spline_select_check(spline)) {
1034                                 BKE_mask_spline_direction_switch(masklay, spline);
1035                                 change = TRUE;
1036                                 change_layer = TRUE;
1037                         }
1038                 }
1039
1040                 if (change_layer) {
1041                         if (IS_AUTOKEY_ON(scene)) {
1042                                 ED_mask_layer_shape_auto_key(masklay, CFRA);
1043                         }
1044                 }
1045         }
1046
1047         if (change) {
1048                 /* TODO: only update this spline */
1049                 BKE_mask_update_display(mask, CTX_data_scene(C)->r.cfra);
1050
1051                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
1052
1053                 return OPERATOR_FINISHED;
1054         }
1055
1056         return OPERATOR_CANCELLED;
1057 }
1058
1059 void MASK_OT_switch_direction(wmOperatorType *ot)
1060 {
1061         /* identifiers */
1062         ot->name = "Switch Direction";
1063         ot->description = "Switch direction of selected splines";
1064         ot->idname = "MASK_OT_switch_direction";
1065
1066         /* api callbacks */
1067         ot->exec = mask_switch_direction_exec;
1068         ot->poll = ED_maskedit_mask_poll;
1069
1070         /* flags */
1071         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1072 }
1073
1074
1075 /* *** recalc normals *** */
1076 static int mask_normals_make_consistent_exec(bContext *C, wmOperator *UNUSED(op))
1077 {
1078         Scene *scene = CTX_data_scene(C);
1079         Mask *mask = CTX_data_edit_mask(C);
1080         MaskLayer *masklay;
1081         int i;
1082
1083         int change = FALSE;
1084
1085         /* do actual selection */
1086         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
1087                 MaskSpline *spline;
1088                 int change_layer = FALSE;
1089
1090                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
1091                         continue;
1092                 }
1093
1094                 for (spline = masklay->splines.first; spline; spline = spline->next) {
1095                         for (i = 0; i < spline->tot_point; i++) {
1096                                 MaskSplinePoint *point = &spline->points[i];
1097
1098                                 if (MASKPOINT_ISSEL_ANY(point)) {
1099                                         BKE_mask_calc_handle_point_auto(spline, point, FALSE);
1100                                         change = TRUE;
1101                                         change_layer = TRUE;
1102                                 }
1103                         }
1104                 }
1105
1106                 if (change_layer) {
1107                         if (IS_AUTOKEY_ON(scene)) {
1108                                 ED_mask_layer_shape_auto_key(masklay, CFRA);
1109                         }
1110                 }
1111         }
1112
1113         if (change) {
1114                 /* TODO: only update this spline */
1115                 BKE_mask_update_display(mask, CTX_data_scene(C)->r.cfra);
1116
1117                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
1118
1119                 return OPERATOR_FINISHED;
1120         }
1121
1122         return OPERATOR_CANCELLED;
1123 }
1124
1125 /* named to match mesh recalc normals */
1126 void MASK_OT_normals_make_consistent(wmOperatorType *ot)
1127 {
1128         /* identifiers */
1129         ot->name = "Recalc Normals";
1130         ot->description = "Re-calculate the direction of selected handles";
1131         ot->idname = "MASK_OT_normals_make_consistent";
1132
1133         /* api callbacks */
1134         ot->exec = mask_normals_make_consistent_exec;
1135         ot->poll = ED_maskedit_mask_poll;
1136
1137         /* flags */
1138         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1139 }
1140
1141
1142 /******************** set handle type *********************/
1143
1144 static int set_handle_type_exec(bContext *C, wmOperator *op)
1145 {
1146         Mask *mask = CTX_data_edit_mask(C);
1147         MaskLayer *masklay;
1148         int handle_type = RNA_enum_get(op->ptr, "type");
1149
1150         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
1151                 MaskSpline *spline;
1152                 int i;
1153
1154                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
1155                         continue;
1156                 }
1157
1158                 for (spline = masklay->splines.first; spline; spline = spline->next) {
1159                         for (i = 0; i < spline->tot_point; i++) {
1160                                 MaskSplinePoint *point = &spline->points[i];
1161
1162                                 if (MASKPOINT_ISSEL_ANY(point)) {
1163                                         BezTriple *bezt = &point->bezt;
1164
1165                                         bezt->h1 = bezt->h2 = handle_type;
1166                                 }
1167                         }
1168                 }
1169         }
1170
1171         WM_event_add_notifier(C, NC_MASK | ND_DATA, mask);
1172         DAG_id_tag_update(&mask->id, 0);
1173
1174         return OPERATOR_FINISHED;
1175 }
1176
1177 void MASK_OT_handle_type_set(wmOperatorType *ot)
1178 {
1179         static EnumPropertyItem editcurve_handle_type_items[] = {
1180                 {HD_AUTO, "AUTO", 0, "Auto", ""},
1181                 {HD_VECT, "VECTOR", 0, "Vector", ""},
1182                 {HD_ALIGN, "ALIGNED", 0, "Aligned", ""},
1183                 {0, NULL, 0, NULL, NULL}
1184         };
1185
1186         /* identifiers */
1187         ot->name = "Set Handle Type";
1188         ot->description = "Set type of handles for selected control points";
1189         ot->idname = "MASK_OT_handle_type_set";
1190
1191         /* api callbacks */
1192         ot->invoke = WM_menu_invoke;
1193         ot->exec = set_handle_type_exec;
1194         ot->poll = ED_maskedit_mask_poll;
1195
1196         /* flags */
1197         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1198
1199         /* properties */
1200         ot->prop = RNA_def_enum(ot->srna, "type", editcurve_handle_type_items, 1, "Type", "Spline type");
1201 }
1202
1203
1204 /* ********* clear/set restrict view *********/
1205 static int mask_hide_view_clear_exec(bContext *C, wmOperator *UNUSED(op))
1206 {
1207         Mask *mask = CTX_data_edit_mask(C);
1208         MaskLayer *masklay;
1209         int changed = FALSE;
1210
1211         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
1212
1213                 if (masklay->restrictflag & OB_RESTRICT_VIEW) {
1214                         ED_mask_layer_select_set(masklay, TRUE);
1215                         masklay->restrictflag &= ~OB_RESTRICT_VIEW;
1216                         changed = 1;
1217                 }
1218         }
1219
1220         if (changed) {
1221                 WM_event_add_notifier(C, NC_MASK | ND_DRAW, mask);
1222                 DAG_id_tag_update(&mask->id, 0);
1223
1224                 return OPERATOR_FINISHED;
1225         }
1226         else {
1227                 return OPERATOR_CANCELLED;
1228         }
1229 }
1230
1231 void MASK_OT_hide_view_clear(wmOperatorType *ot)
1232 {
1233
1234         /* identifiers */
1235         ot->name = "Clear Restrict View";
1236         ot->description = "Reveal the layer by setting the hide flag";
1237         ot->idname = "MASK_OT_hide_view_clear";
1238
1239         /* api callbacks */
1240         ot->exec = mask_hide_view_clear_exec;
1241         ot->poll = ED_maskedit_mask_poll;
1242
1243         /* flags */
1244         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1245 }
1246
1247 static int mask_hide_view_set_exec(bContext *C, wmOperator *op)
1248 {
1249         Mask *mask = CTX_data_edit_mask(C);
1250         MaskLayer *masklay;
1251         const int unselected = RNA_boolean_get(op->ptr, "unselected");
1252         int changed = FALSE;
1253
1254         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
1255
1256                 if (masklay->restrictflag & MASK_RESTRICT_SELECT) {
1257                         continue;
1258                 }
1259
1260                 if (!unselected) {
1261                         if (ED_mask_layer_select_check(masklay)) {
1262                                 ED_mask_layer_select_set(masklay, FALSE);
1263
1264                                 masklay->restrictflag |= OB_RESTRICT_VIEW;
1265                                 changed = 1;
1266                                 if (masklay == BKE_mask_layer_active(mask)) {
1267                                         BKE_mask_layer_active_set(mask, NULL);
1268                                 }
1269                         }
1270                 }
1271                 else {
1272                         if (!ED_mask_layer_select_check(masklay)) {
1273                                 masklay->restrictflag |= OB_RESTRICT_VIEW;
1274                                 changed = 1;
1275                                 if (masklay == BKE_mask_layer_active(mask)) {
1276                                         BKE_mask_layer_active_set(mask, NULL);
1277                                 }
1278                         }
1279                 }
1280         }
1281
1282         if (changed) {
1283                 WM_event_add_notifier(C, NC_MASK | ND_DRAW, mask);
1284                 DAG_id_tag_update(&mask->id, 0);
1285
1286                 return OPERATOR_FINISHED;
1287         }
1288         else {
1289                 return OPERATOR_CANCELLED;
1290         }
1291 }
1292
1293 void MASK_OT_hide_view_set(wmOperatorType *ot)
1294 {
1295         /* identifiers */
1296         ot->name = "Set Restrict View";
1297         ot->description = "Hide the layer by setting the hide flag";
1298         ot->idname = "MASK_OT_hide_view_set";
1299
1300         /* api callbacks */
1301         ot->exec = mask_hide_view_set_exec;
1302         ot->poll = ED_maskedit_mask_poll;
1303
1304         /* flags */
1305         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1306
1307         RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected layers");
1308 }
1309
1310
1311 static int mask_feather_weight_clear_exec(bContext *C, wmOperator *UNUSED(op))
1312 {
1313         Mask *mask = CTX_data_edit_mask(C);
1314         MaskLayer *masklay;
1315         int changed = FALSE;
1316         int i;
1317
1318         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
1319                 MaskSpline *spline;
1320
1321                 if (masklay->restrictflag & (MASK_RESTRICT_SELECT | MASK_RESTRICT_VIEW)) {
1322                         continue;
1323                 }
1324
1325                 for (spline = masklay->splines.first; spline; spline = spline->next) {
1326                         for (i = 0; i < spline->tot_point; i++) {
1327                                 MaskSplinePoint *point = &spline->points[i];
1328
1329                                 if (MASKPOINT_ISSEL_ANY(point)) {
1330                                         BezTriple *bezt = &point->bezt;
1331                                         bezt->weight = 0.0f;
1332                                         changed = TRUE;
1333                                 }
1334                         }
1335                 }
1336         }
1337
1338         if (changed) {
1339                 /* TODO: only update edited splines */
1340                 BKE_mask_update_display(mask, CTX_data_scene(C)->r.cfra);
1341
1342                 WM_event_add_notifier(C, NC_MASK | ND_DRAW, mask);
1343                 DAG_id_tag_update(&mask->id, 0);
1344
1345                 return OPERATOR_FINISHED;
1346         }
1347         else {
1348                 return OPERATOR_CANCELLED;
1349         }
1350 }
1351
1352 void MASK_OT_feather_weight_clear(wmOperatorType *ot)
1353 {
1354         /* identifiers */
1355         ot->name = "Clear Feather Weight";
1356         ot->description = "Reset the feather weight to zero";
1357         ot->idname = "MASK_OT_feather_weight_clear";
1358
1359         /* api callbacks */
1360         ot->exec = mask_feather_weight_clear_exec;
1361         ot->poll = ED_maskedit_mask_poll;
1362
1363         /* flags */
1364         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1365 }
1366
1367 /******************** move mask layer operator *********************/
1368
1369 static int mask_layer_move_poll(bContext *C)
1370 {
1371         if (ED_maskedit_mask_poll(C)) {
1372                 Mask *mask = CTX_data_edit_mask(C);
1373
1374                 return mask->masklay_tot > 0;
1375         }
1376
1377         return FALSE;
1378 }
1379
1380 static int mask_layer_move_exec(bContext *C, wmOperator *op)
1381 {
1382         Mask *mask = CTX_data_edit_mask(C);
1383         MaskLayer *mask_layer = BLI_findlink(&mask->masklayers, mask->masklay_act);
1384         MaskLayer *mask_layer_other;
1385         int direction = RNA_enum_get(op->ptr, "direction");
1386
1387         if (!mask_layer)
1388                 return OPERATOR_CANCELLED;
1389
1390         if (direction == -1) {
1391                 mask_layer_other = mask_layer->prev;
1392
1393                 if (!mask_layer_other)
1394                         return OPERATOR_CANCELLED;
1395
1396                 BLI_remlink(&mask->masklayers, mask_layer);
1397                 BLI_insertlinkbefore(&mask->masklayers, mask_layer_other, mask_layer);
1398                 mask->masklay_act--;
1399         }
1400         else if (direction == 1) {
1401                 mask_layer_other = mask_layer->next;
1402
1403                 if (!mask_layer_other)
1404                         return OPERATOR_CANCELLED;
1405
1406                 BLI_remlink(&mask->masklayers, mask_layer);
1407                 BLI_insertlinkafter(&mask->masklayers, mask_layer_other, mask_layer);
1408                 mask->masklay_act++;
1409         }
1410
1411         return OPERATOR_FINISHED;
1412 }
1413
1414 void MASK_OT_layer_move(wmOperatorType *ot)
1415 {
1416         static EnumPropertyItem direction_items[] = {
1417                 {-1, "UP", 0, "Up", ""},
1418                 {1, "DOWN", 0, "Down", ""},
1419                 {0, NULL, 0, NULL, NULL}
1420         };
1421
1422         /* identifiers */
1423         ot->name = "Move Layer";
1424         ot->description = "Move the active layer up/down in the list";
1425         ot->idname = "MASK_OT_layer_move";
1426
1427         /* api callbacks */
1428         ot->exec = mask_layer_move_exec;
1429         ot->poll = mask_layer_move_poll;
1430
1431         /* flags */
1432         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1433
1434         /* properties */
1435         RNA_def_enum(ot->srna, "direction", direction_items, 0, "Direction", "Direction to move the active layer");
1436 }