b569469d3044b73f2cbfcf9c2230a9e3684038e4
[blender.git] / source / blender / editors / space_clip / clip_graph_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) 2011 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/space_clip/clip_graph_ops.c
29  *  \ingroup spclip
30  */
31
32 #include "DNA_object_types.h"   /* SELECT */
33 #include "DNA_scene_types.h"
34
35 #include "MEM_guardedalloc.h"
36
37 #include "BLI_utildefines.h"
38 #include "BLI_math.h"
39 #include "BLI_listbase.h"
40 #include "BLI_rect.h"
41
42 #include "BKE_context.h"
43 #include "BKE_movieclip.h"
44 #include "BKE_tracking.h"
45 #include "BKE_depsgraph.h"
46
47 #include "WM_api.h"
48 #include "WM_types.h"
49
50 #include "ED_screen.h"
51 #include "ED_clip.h"
52
53 #include "UI_interface.h"
54
55 #include "RNA_access.h"
56 #include "RNA_define.h"
57
58 #include "UI_view2d.h"
59
60 #include "clip_intern.h"        // own include
61
62 /******************** common graph-editing utilities ********************/
63
64 static int ED_space_clip_graph_poll(bContext *C)
65 {
66         SpaceClip *sc = CTX_wm_space_clip(C);
67
68         if (sc && sc->clip) {
69                 ARegion *ar = CTX_wm_region(C);
70
71                 return ar->regiontype == RGN_TYPE_PREVIEW;
72         }
73
74         return 0;
75 }
76
77 typedef struct {
78         int action;
79 } SelectUserData;
80
81 static void toggle_selection_cb(void *userdata, MovieTrackingMarker *marker)
82 {
83         SelectUserData *data = (SelectUserData *)userdata;
84
85         switch (data->action) {
86                 case SEL_SELECT:
87                         marker->flag |= MARKER_GRAPH_SEL;
88                         break;
89                 case SEL_DESELECT:
90                         marker->flag &= ~MARKER_GRAPH_SEL;
91                         break;
92                 case SEL_INVERT:
93                         marker->flag ^= MARKER_GRAPH_SEL;
94                         break;
95         }
96 }
97
98 /******************** mouse select operator ********************/
99
100 typedef struct {
101         int coord,              /* coordinate index of found entuty (0 = X-axis, 1 = Y-axis) */
102             has_prev;           /* if there's valid coordinate of previous point of curve segment */
103
104         float min_dist,         /* minimal distance between mouse and currently found entuty */
105               mouse_co[2],      /* mouse coordinate */
106               prev_co[2],       /* coordinate of previeous point of segment */
107               min_co[2];        /* coordinate of entity with minimal distance */
108
109         MovieTrackingTrack *track;      /* nearest found track */
110         MovieTrackingMarker *marker;    /* nearest found marker */
111 } MouseSelectUserData;
112
113 static void find_nearest_tracking_segment_cb(void *userdata, MovieTrackingTrack *track,
114                                              MovieTrackingMarker *marker, int coord, float val)
115 {
116         MouseSelectUserData *data = userdata;
117         float co[2] = {marker->framenr, val};
118
119         if (data->has_prev) {
120                 float d = dist_to_line_segment_v2(data->mouse_co, data->prev_co, co);
121
122                 if (data->track == NULL || d < data->min_dist) {
123                         data->track = track;
124                         data->min_dist = d;
125                         data->coord = coord;
126                         copy_v2_v2(data->min_co, co);
127                 }
128         }
129
130         data->has_prev = TRUE;
131         copy_v2_v2(data->prev_co, co);
132 }
133
134 void find_nearest_tracking_segment_end_cb(void *userdata)
135 {
136         MouseSelectUserData *data = userdata;
137
138         data->has_prev = FALSE;
139 }
140
141 static void find_nearest_tracking_knot_cb(void *userdata, MovieTrackingTrack *track,
142                                           MovieTrackingMarker *marker, int coord, float val)
143 {
144         MouseSelectUserData *data = userdata;
145         float dx = marker->framenr - data->mouse_co[0], dy = val - data->mouse_co[1];
146         float d = dx * dx + dy * dy;
147
148         if (data->marker == NULL || d < data->min_dist) {
149                 float co[2]= {marker->framenr, val};
150
151                 data->track = track;
152                 data->marker = marker;
153                 data->min_dist = d;
154                 data->coord = coord;
155                 copy_v2_v2(data->min_co, co);
156         }
157
158 }
159
160 static void mouse_select_init_data(MouseSelectUserData *userdata, float *co)
161 {
162         memset(userdata, 0, sizeof(MouseSelectUserData));
163         userdata->min_dist = FLT_MAX;
164         copy_v2_v2(userdata->mouse_co, co);
165 }
166
167 static int mouse_select_knot(bContext *C, float co[2], int extend)
168 {
169         SpaceClip *sc = CTX_wm_space_clip(C);
170         MovieClip *clip= ED_space_clip(sc);
171         ARegion *ar = CTX_wm_region(C);
172         View2D *v2d = &ar->v2d;
173         MovieTracking *tracking = &clip->tracking;
174         MovieTrackingTrack *act_track = BKE_tracking_active_track(tracking);
175         static const int delta = 6;
176
177         if (act_track) {
178                 MouseSelectUserData userdata;
179
180                 mouse_select_init_data(&userdata, co);
181                 clip_graph_tracking_values_iterate_track(sc, act_track, &userdata,
182                                                          find_nearest_tracking_knot_cb, NULL, NULL);
183
184                 if (userdata.marker) {
185                         int x1, y1, x2, y2;
186
187                         UI_view2d_view_to_region(v2d, co[0], co[1], &x1, &y1);
188                         UI_view2d_view_to_region(v2d, userdata.min_co[0], userdata.min_co[1], &x2, &y2);
189
190                         if (abs(x2 - x1) <= delta && abs(y2 - y1) <= delta) {
191                                 if (!extend) {
192                                         SelectUserData selectdata = {SEL_DESELECT};
193
194                                         clip_graph_tracking_iterate(sc, &selectdata, toggle_selection_cb);
195                                 }
196
197                                 if (userdata.coord == 0)
198                                         userdata.marker->flag |= MARKER_GRAPH_SEL_X;
199                                 else
200                                         userdata.marker->flag |= MARKER_GRAPH_SEL_Y;
201
202                                 return TRUE;
203                         }
204                 }
205         }
206
207         return FALSE;
208 }
209
210 static int mouse_select_curve(bContext *C, float co[2], int extend)
211 {
212         SpaceClip *sc = CTX_wm_space_clip(C);
213         MovieClip *clip = ED_space_clip(sc);
214         MovieTracking *tracking = &clip->tracking;
215         MovieTrackingTrack *act_track = BKE_tracking_active_track(tracking);
216         MouseSelectUserData userdata;
217
218         mouse_select_init_data(&userdata, co);
219         clip_graph_tracking_values_iterate(sc, &userdata, find_nearest_tracking_segment_cb,
220                                            NULL, find_nearest_tracking_segment_end_cb);
221
222         if (userdata.track) {
223                 if (extend) {
224                         if (act_track == userdata.track) {
225                                 /* currently only single curve can be selected (selected curve represents active track) */
226                                 act_track = NULL;
227                         }
228                 }
229                 else if (act_track != userdata.track) {
230                         MovieTrackingMarker *marker;
231                         SelectUserData selectdata = {SEL_DESELECT};
232
233                         tracking->act_track = userdata.track;
234
235                         /* make active track be centered to screen */
236                         marker = BKE_tracking_get_marker(userdata.track, sc->user.framenr);
237
238                         clip_view_center_to_point(sc, marker->pos[0], marker->pos[1]);
239
240                         /* deselect all knots on newly selected curve */
241                         clip_graph_tracking_iterate(sc, &selectdata, toggle_selection_cb);
242                 }
243
244                 return TRUE;
245         }
246
247         return FALSE;
248 }
249
250 static int mouse_select(bContext *C, float co[2], int extend)
251 {
252         int sel = FALSE;
253
254         /* first try to select knot on selected curves */
255         sel = mouse_select_knot(C, co, extend);
256
257         if (!sel) {
258                 /* if there's no close enough knot to mouse osition, select nearest curve */
259                 sel = mouse_select_curve(C, co, extend);
260         }
261
262         if (sel)
263                 WM_event_add_notifier(C, NC_GEOM|ND_SELECT, NULL);
264
265         return OPERATOR_FINISHED;
266 }
267
268 static int select_exec(bContext *C, wmOperator *op)
269 {
270         float co[2];
271         int  extend = RNA_boolean_get(op->ptr, "extend");
272
273         RNA_float_get_array(op->ptr, "location", co);
274
275         return mouse_select(C, co, extend);
276 }
277
278 static int select_invoke(bContext *C, wmOperator *op, wmEvent *event)
279 {
280         ARegion *ar = CTX_wm_region(C);
281         float co[2];
282
283         UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
284         RNA_float_set_array(op->ptr, "location", co);
285
286         return select_exec(C, op);
287 }
288
289 void CLIP_OT_graph_select(wmOperatorType *ot)
290 {
291         /* identifiers */
292         ot->name = "Select";
293         ot->description = "Select graph curves";
294         ot->idname = "CLIP_OT_graph_select";
295
296         /* api callbacks */
297         ot->exec = select_exec;
298         ot->invoke = select_invoke;
299         ot->poll = ED_space_clip_graph_poll;
300
301         /* flags */
302         ot->flag = OPTYPE_UNDO;
303
304         /* properties */
305         RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX,
306                 "Location", "Mouse location to select nearest entity", -100.0f, 100.0f);
307         RNA_def_boolean(ot->srna, "extend", 0,
308                 "Extend", "Extend selection rather than clearing the existing selection");
309 }
310
311 /********************** border select operator *********************/
312
313 typedef struct BorderSelectuserData {
314         rctf rect;
315         int change, mode, extend;
316 } BorderSelectuserData;
317
318 static void border_select_cb(void *userdata, MovieTrackingTrack *UNUSED(track),
319                              MovieTrackingMarker *marker, int coord, float val)
320 {
321         BorderSelectuserData *data = (BorderSelectuserData *) userdata;
322
323         if (BLI_in_rctf(&data->rect, marker->framenr, val)) {
324                 int flag = 0;
325
326                 if (coord == 0)
327                         flag = MARKER_GRAPH_SEL_X;
328                 else
329                         flag = MARKER_GRAPH_SEL_Y;
330
331                 if (data->mode == GESTURE_MODAL_SELECT)
332                         marker->flag |= flag;
333                 else
334                         marker->flag &= ~flag;
335
336                 data->change = TRUE;
337         }
338         else if (!data->extend) {
339                 marker->flag&= ~MARKER_GRAPH_SEL;
340         }
341 }
342
343 static int border_select_graph_exec(bContext *C, wmOperator *op)
344 {
345         SpaceClip *sc = CTX_wm_space_clip(C);
346         ARegion *ar = CTX_wm_region(C);
347         MovieClip *clip = ED_space_clip(sc);
348         MovieTracking *tracking = &clip->tracking;
349         MovieTrackingTrack *act_track = BKE_tracking_active_track(tracking);
350         BorderSelectuserData userdata;
351         rcti rect;
352
353         /* get rectangle from operator */
354         rect.xmin = RNA_int_get(op->ptr, "xmin");
355         rect.ymin = RNA_int_get(op->ptr, "ymin");
356         rect.xmax = RNA_int_get(op->ptr, "xmax");
357         rect.ymax = RNA_int_get(op->ptr, "ymax");
358
359         UI_view2d_region_to_view(&ar->v2d, rect.xmin, rect.ymin, &userdata.rect.xmin, &userdata.rect.ymin);
360         UI_view2d_region_to_view(&ar->v2d, rect.xmax, rect.ymax, &userdata.rect.xmax, &userdata.rect.ymax);
361
362         userdata.change = FALSE;
363         userdata.mode = RNA_int_get(op->ptr, "gesture_mode");
364         userdata.extend = RNA_boolean_get(op->ptr, "extend");
365
366         clip_graph_tracking_values_iterate_track(sc, act_track, &userdata, border_select_cb, NULL, NULL);
367
368         if (userdata.change) {
369                 WM_event_add_notifier(C, NC_GEOM|ND_SELECT, NULL);
370
371                 return OPERATOR_FINISHED;
372         }
373
374         return OPERATOR_CANCELLED;
375 }
376
377 void CLIP_OT_graph_select_border(wmOperatorType *ot)
378 {
379         /* identifiers */
380         ot->name = "Border Select";
381         ot->description = "Select curve points using border selection";
382         ot->idname = "CLIP_OT_graph_select_border";
383
384         /* api callbacks */
385         ot->invoke = WM_border_select_invoke;
386         ot->exec = border_select_graph_exec;
387         ot->modal = WM_border_select_modal;
388         ot->poll = ED_space_clip_graph_poll;
389
390         /* flags */
391         ot->flag = OPTYPE_UNDO;
392
393         /* properties */
394         WM_operator_properties_gesture_border(ot, TRUE);
395 }
396
397 /********************** select all operator *********************/
398
399 static int graph_select_all_markers_exec(bContext *C, wmOperator *op)
400 {
401         SpaceClip *sc = CTX_wm_space_clip(C);
402         MovieClip *clip = ED_space_clip(sc);
403         MovieTracking *tracking = &clip->tracking;
404         MovieTrackingTrack *act_track = BKE_tracking_active_track(tracking);
405         MovieTrackingMarker *marker;
406         int action = RNA_enum_get(op->ptr, "action");
407         int a;
408
409         if (!act_track)
410                 return OPERATOR_CANCELLED;
411
412         if (action == SEL_TOGGLE) {
413                 action = SEL_SELECT;
414
415                 for (a = 0; a < act_track->markersnr; a++) {
416                         marker = &act_track->markers[a];
417
418                         if (marker->flag & MARKER_GRAPH_SEL) {
419                                 action = SEL_DESELECT;
420                                 break;
421                         }
422                 }
423         }
424
425         for (a = 0; a < act_track->markersnr; a++) {
426                 marker = &act_track->markers[a];
427
428                 switch (action) {
429                         case SEL_SELECT:
430                                 marker->flag |= MARKER_GRAPH_SEL;
431                                 break;
432                         case SEL_DESELECT:
433                                 marker->flag &= ~MARKER_GRAPH_SEL;
434                                 break;
435                         case SEL_INVERT:
436                                 marker->flag ^= MARKER_GRAPH_SEL;
437                                 break;
438                 }
439         }
440
441         WM_event_add_notifier(C, NC_GEOM|ND_SELECT, NULL);
442
443         return OPERATOR_FINISHED;
444 }
445
446 void CLIP_OT_graph_select_all_markers(wmOperatorType *ot)
447 {
448         /* identifiers */
449         ot->name = "(De)select All Markers";
450         ot->description = "Change selection of all markers of active track";
451         ot->idname = "CLIP_OT_graph_select_all_markers";
452
453         /* api callbacks */
454         ot->exec = graph_select_all_markers_exec;
455         ot->poll = ED_space_clip_graph_poll;
456
457         /* flags */
458         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
459
460         WM_operator_properties_select_all(ot);
461 }
462
463 /******************** delete curve operator ********************/
464
465 static int delete_curve_exec(bContext *C, wmOperator *UNUSED(op))
466 {
467         SpaceClip *sc = CTX_wm_space_clip(C);
468         MovieClip *clip = ED_space_clip(sc);
469         MovieTracking *tracking = &clip->tracking;
470         ListBase *tracksbase = BKE_tracking_get_tracks(tracking);
471         MovieTrackingTrack *act_track = BKE_tracking_active_track(tracking);
472
473         if (act_track)
474                 clip_delete_track(C, clip, tracksbase, act_track);
475
476         return OPERATOR_FINISHED;
477 }
478
479 void CLIP_OT_graph_delete_curve(wmOperatorType *ot)
480 {
481         /* identifiers */
482         ot->name = "Delete Curve";
483         ot->description = "Delete selected curves";
484         ot->idname = "CLIP_OT_graph_delete_curve";
485
486         /* api callbacks */
487         ot->invoke = WM_operator_confirm;
488         ot->exec = delete_curve_exec;
489         ot->poll = ED_space_clip_poll;
490
491         /* flags */
492         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
493 }
494
495 /******************** delete knot operator ********************/
496
497 static int delete_knot_exec(bContext *C, wmOperator *UNUSED(op))
498 {
499         SpaceClip *sc = CTX_wm_space_clip(C);
500         MovieClip *clip= ED_space_clip(sc);
501         MovieTracking *tracking = &clip->tracking;
502         ListBase *tracksbase = BKE_tracking_get_tracks(tracking);
503         MovieTrackingTrack *act_track = BKE_tracking_active_track(tracking);
504
505         if (act_track) {
506                 int a = 0;
507
508                 while (a < act_track->markersnr) {
509                         MovieTrackingMarker *marker = &act_track->markers[a];
510
511                         if (marker->flag & MARKER_GRAPH_SEL)
512                                 clip_delete_marker(C, clip, tracksbase, act_track, marker);
513                         else
514                                 a++;
515                 }
516         }
517
518         return OPERATOR_FINISHED;
519 }
520
521 void CLIP_OT_graph_delete_knot(wmOperatorType *ot)
522 {
523         /* identifiers */
524         ot->name = "Delete Knot";
525         ot->description = "Delete curve knots";
526         ot->idname = "CLIP_OT_graph_delete_knot";
527
528         /* api callbacks */
529         ot->exec = delete_knot_exec;
530         ot->poll = ED_space_clip_graph_poll;
531
532         /* flags */
533         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
534 }
535
536 /******************** view all operator ********************/
537
538 typedef struct {
539         float min, max;
540 } ViewAllUserData;
541
542 static void view_all_cb(void *userdata, MovieTrackingTrack *UNUSED(track), MovieTrackingMarker *UNUSED(marker),
543                         int UNUSED(coord), float val)
544 {
545         ViewAllUserData *data = (ViewAllUserData *) userdata;
546
547         if (val < data->min)
548                 data->min = val;
549
550         if (val > data->max)
551                 data->max = val;
552 }
553
554 static int view_all_exec(bContext *C, wmOperator *UNUSED(op))
555 {
556         Scene *scene = CTX_data_scene(C);
557         ARegion *ar = CTX_wm_region(C);
558         SpaceClip *sc = CTX_wm_space_clip(C);
559         View2D *v2d = &ar->v2d;
560         ViewAllUserData userdata;
561         float extra;
562
563         userdata.max = -FLT_MAX;
564         userdata.min = FLT_MAX;
565
566         clip_graph_tracking_values_iterate(sc, &userdata, view_all_cb, NULL, NULL);
567
568         /* set extents of view to start/end frames */
569         v2d->cur.xmin = (float) SFRA;
570         v2d->cur.xmax = (float) EFRA;
571
572         if (userdata.min < userdata.max) {
573                 v2d->cur.ymin = userdata.min;
574                 v2d->cur.ymax = userdata.max;
575         }
576         else {
577                 v2d->cur.ymin = -10;
578                 v2d->cur.ymax = 10;
579         }
580
581         /* we need an extra "buffer" factor on either side so that the endpoints are visible */
582         extra = 0.01f * (v2d->cur.xmax - v2d->cur.xmin);
583         v2d->cur.xmin -= extra;
584         v2d->cur.xmax += extra;
585
586         extra = 0.01f * (v2d->cur.ymax - v2d->cur.ymin);
587         v2d->cur.ymin -= extra;
588         v2d->cur.ymax += extra;
589
590         ED_region_tag_redraw(ar);
591
592         return OPERATOR_FINISHED;
593 }
594
595 void CLIP_OT_graph_view_all(wmOperatorType *ot)
596 {
597         /* identifiers */
598         ot->name = "View All";
599         ot->description = "View all curves in editor";
600         ot->idname = "CLIP_OT_graph_view_all";
601
602         /* api callbacks */
603         ot->exec = view_all_exec;
604         ot->poll = ED_space_clip_graph_poll;
605 }
606
607 /******************** jump to current frame operator ********************/
608
609 void ED_clip_graph_center_current_frame(Scene *scene, ARegion *ar)
610 {
611         View2D *v2d = &ar->v2d;
612         float extra = (v2d->cur.xmax - v2d->cur.xmin) / 2.0f;
613
614         /* set extents of view to start/end frames */
615         v2d->cur.xmin = (float)CFRA - extra;
616         v2d->cur.xmax = (float)CFRA + extra;
617 }
618
619 static int center_current_frame_exec(bContext *C, wmOperator *UNUSED(op))
620 {
621         Scene *scene = CTX_data_scene(C);
622         ARegion *ar = CTX_wm_region(C);
623
624         ED_clip_graph_center_current_frame(scene, ar);
625
626         ED_region_tag_redraw(ar);
627
628         return OPERATOR_FINISHED;
629 }
630
631 void CLIP_OT_graph_center_current_frame(wmOperatorType *ot)
632 {
633         /* identifiers */
634         ot->name = "Center Current Frame";
635         ot->description = "Scroll view so current frame would be centered";
636         ot->idname = "CLIP_OT_graph_center_current_frame";
637
638         /* api callbacks */
639         ot->exec = center_current_frame_exec;
640         ot->poll = ED_space_clip_graph_poll;
641 }
642
643 /********************** disable markers operator *********************/
644
645 static int graph_disable_markers_exec(bContext *C, wmOperator *op)
646 {
647         SpaceClip *sc = CTX_wm_space_clip(C);
648         MovieClip *clip = ED_space_clip(sc);
649         MovieTracking *tracking = &clip->tracking;
650         MovieTrackingTrack *act_track = BKE_tracking_active_track(tracking);
651         MovieTrackingMarker *marker;
652         int action = RNA_enum_get(op->ptr, "action");
653         int a;
654
655         if (!act_track || (act_track->flag & TRACK_LOCKED))
656                 return OPERATOR_CANCELLED;
657
658         for (a = 0; a < act_track->markersnr; a++) {
659                 marker = &act_track->markers[a];
660
661                 if (marker->flag & MARKER_GRAPH_SEL) {
662                         if (action==0)
663                                 marker->flag |= MARKER_DISABLED;
664                         else if (action==1)
665                                 marker->flag &= ~MARKER_DISABLED;
666                         else
667                                 marker->flag ^= MARKER_DISABLED;
668                 }
669         }
670
671         DAG_id_tag_update(&clip->id, 0);
672
673         WM_event_add_notifier(C, NC_MOVIECLIP|NA_EVALUATED, clip);
674
675         return OPERATOR_FINISHED;
676 }
677
678 void CLIP_OT_graph_disable_markers(wmOperatorType *ot)
679 {
680         static EnumPropertyItem actions_items[] = {
681                         {0, "DISABLE", 0, "Disable", "Disable selected markers"},
682                         {1, "ENABLE", 0, "Enable", "Enable selected markers"},
683                         {2, "TOGGLE", 0, "Toggle", "Toggle disabled flag for selected markers"},
684                         {0, NULL, 0, NULL, NULL}
685         };
686
687         /* identifiers */
688         ot->name = "Disable Markers";
689         ot->description = "Disable/enable selected markers";
690         ot->idname = "CLIP_OT_graph_disable_markers";
691
692         /* api callbacks */
693         ot->exec = graph_disable_markers_exec;
694         ot->poll = ED_space_clip_graph_poll;
695
696         /* flags */
697         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
698
699         /* properties */
700         RNA_def_enum(ot->srna, "action", actions_items, 0, "Action", "Disable action to execute");
701 }