Cleanup: style, use braces for editors
[blender.git] / source / blender / editors / sculpt_paint / paint_curve.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16
17 /** \file
18  * \ingroup edsculpt
19  */
20
21 #include <string.h>
22 #include <limits.h>
23
24 #include "MEM_guardedalloc.h"
25
26 #include "DNA_brush_types.h"
27 #include "DNA_object_types.h"
28 #include "DNA_screen_types.h"
29 #include "DNA_space_types.h"
30 #include "DNA_view3d_types.h"
31
32 #include "BLI_math_vector.h"
33
34 #include "BKE_context.h"
35 #include "BKE_main.h"
36 #include "BKE_paint.h"
37
38 #include "DEG_depsgraph.h"
39
40 #include "ED_view3d.h"
41 #include "ED_paint.h"
42
43 #include "WM_api.h"
44 #include "WM_types.h"
45
46 #include "RNA_access.h"
47 #include "RNA_define.h"
48
49 #include "UI_view2d.h"
50
51 #include "paint_intern.h"
52
53 #define PAINT_CURVE_SELECT_THRESHOLD 40.0f
54 #define PAINT_CURVE_POINT_SELECT(pcp, i) (*(&pcp->bez.f1 + i) = SELECT)
55
56 bool paint_curve_poll(bContext *C)
57 {
58   Object *ob = CTX_data_active_object(C);
59   Paint *p;
60   RegionView3D *rv3d = CTX_wm_region_view3d(C);
61   SpaceImage *sima;
62
63   if (rv3d && !(ob && ((ob->mode & OB_MODE_ALL_PAINT) != 0))) {
64     return false;
65   }
66
67   sima = CTX_wm_space_image(C);
68
69   if (sima && sima->mode != SI_MODE_PAINT) {
70     return false;
71   }
72
73   p = BKE_paint_get_active_from_context(C);
74
75   if (p && p->brush && (p->brush->flag & BRUSH_CURVE)) {
76     return true;
77   }
78
79   return false;
80 }
81
82 #define SEL_F1 (1 << 0)
83 #define SEL_F2 (1 << 1)
84 #define SEL_F3 (1 << 2)
85
86 /* returns 0, 1, or 2 in point according to handle 1, pivot or handle 2 */
87 static PaintCurvePoint *paintcurve_point_get_closest(
88     PaintCurve *pc, const float pos[2], bool ignore_pivot, const float threshold, char *point)
89 {
90   PaintCurvePoint *pcp, *closest = NULL;
91   int i;
92   float dist, closest_dist = FLT_MAX;
93
94   for (i = 0, pcp = pc->points; i < pc->tot_points; i++, pcp++) {
95     dist = len_manhattan_v2v2(pos, pcp->bez.vec[0]);
96     if (dist < threshold) {
97       if (dist < closest_dist) {
98         closest = pcp;
99         closest_dist = dist;
100         if (point) {
101           *point = SEL_F1;
102         }
103       }
104     }
105     if (!ignore_pivot) {
106       dist = len_manhattan_v2v2(pos, pcp->bez.vec[1]);
107       if (dist < threshold) {
108         if (dist < closest_dist) {
109           closest = pcp;
110           closest_dist = dist;
111           if (point) {
112             *point = SEL_F2;
113           }
114         }
115       }
116     }
117     dist = len_manhattan_v2v2(pos, pcp->bez.vec[2]);
118     if (dist < threshold) {
119       if (dist < closest_dist) {
120         closest = pcp;
121         closest_dist = dist;
122         if (point) {
123           *point = SEL_F3;
124         }
125       }
126     }
127   }
128
129   return closest;
130 }
131
132 static int paintcurve_point_co_index(char sel)
133 {
134   char i = 0;
135   while (sel != 1) {
136     sel >>= 1;
137     i++;
138   }
139   return i;
140 }
141
142 static char paintcurve_point_side_index(const BezTriple *bezt,
143                                         const bool is_first,
144                                         const char fallback)
145 {
146   /* when matching, guess based on endpoint side */
147   if (BEZT_ISSEL_ANY(bezt)) {
148     if ((bezt->f1 & SELECT) == (bezt->f3 & SELECT)) {
149       return is_first ? SEL_F1 : SEL_F3;
150     }
151     else if (bezt->f1 & SELECT) {
152       return SEL_F1;
153     }
154     else if (bezt->f3 & SELECT) {
155       return SEL_F3;
156     }
157     else {
158       return fallback;
159     }
160   }
161   else {
162     return 0;
163   }
164 }
165
166 /******************* Operators *********************************/
167
168 static int paintcurve_new_exec(bContext *C, wmOperator *UNUSED(op))
169 {
170   Paint *p = BKE_paint_get_active_from_context(C);
171   Main *bmain = CTX_data_main(C);
172
173   if (p && p->brush) {
174     p->brush->paint_curve = BKE_paint_curve_add(bmain, "PaintCurve");
175   }
176
177   WM_event_add_notifier(C, NC_PAINTCURVE | NA_ADDED, NULL);
178
179   return OPERATOR_FINISHED;
180 }
181
182 void PAINTCURVE_OT_new(wmOperatorType *ot)
183 {
184   /* identifiers */
185   ot->name = "Add New Paint Curve";
186   ot->description = "Add new paint curve";
187   ot->idname = "PAINTCURVE_OT_new";
188
189   /* api callbacks */
190   ot->exec = paintcurve_new_exec;
191   ot->poll = paint_curve_poll;
192
193   /* flags */
194   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
195 }
196
197 static void paintcurve_point_add(bContext *C, wmOperator *op, const int loc[2])
198 {
199   Paint *p = BKE_paint_get_active_from_context(C);
200   Brush *br = p->brush;
201   Main *bmain = CTX_data_main(C);
202   PaintCurve *pc;
203   PaintCurvePoint *pcp;
204   wmWindow *window = CTX_wm_window(C);
205   ARegion *ar = CTX_wm_region(C);
206   float vec[3] = {loc[0], loc[1], 0.0};
207   int add_index;
208   int i;
209
210   pc = br->paint_curve;
211   if (!pc) {
212     br->paint_curve = pc = BKE_paint_curve_add(bmain, "PaintCurve");
213   }
214
215   ED_paintcurve_undo_push_begin(op->type->name);
216
217   pcp = MEM_mallocN((pc->tot_points + 1) * sizeof(PaintCurvePoint), "PaintCurvePoint");
218   add_index = pc->add_index;
219
220   if (pc->points) {
221     if (add_index > 0) {
222       memcpy(pcp, pc->points, add_index * sizeof(PaintCurvePoint));
223     }
224     if (add_index < pc->tot_points) {
225       memcpy(pcp + add_index + 1,
226              pc->points + add_index,
227              (pc->tot_points - add_index) * sizeof(PaintCurvePoint));
228     }
229
230     MEM_freeN(pc->points);
231   }
232   pc->points = pcp;
233   pc->tot_points++;
234
235   /* initialize new point */
236   memset(&pcp[add_index], 0, sizeof(PaintCurvePoint));
237   copy_v3_v3(pcp[add_index].bez.vec[0], vec);
238   copy_v3_v3(pcp[add_index].bez.vec[1], vec);
239   copy_v3_v3(pcp[add_index].bez.vec[2], vec);
240
241   /* last step, clear selection from all bezier handles expect the next */
242   for (i = 0; i < pc->tot_points; i++) {
243     pcp[i].bez.f1 = pcp[i].bez.f2 = pcp[i].bez.f3 = 0;
244   }
245
246   BKE_paint_curve_clamp_endpoint_add_index(pc, add_index);
247
248   if (pc->add_index != 0) {
249     pcp[add_index].bez.f3 = SELECT;
250     pcp[add_index].bez.h2 = HD_ALIGN;
251   }
252   else {
253     pcp[add_index].bez.f1 = SELECT;
254     pcp[add_index].bez.h1 = HD_ALIGN;
255   }
256
257   ED_paintcurve_undo_push_end();
258
259   WM_paint_cursor_tag_redraw(window, ar);
260 }
261
262 static int paintcurve_add_point_invoke(bContext *C, wmOperator *op, const wmEvent *event)
263 {
264   int loc[2] = {event->mval[0], event->mval[1]};
265   paintcurve_point_add(C, op, loc);
266   RNA_int_set_array(op->ptr, "location", loc);
267   return OPERATOR_FINISHED;
268 }
269
270 static int paintcurve_add_point_exec(bContext *C, wmOperator *op)
271 {
272   int loc[2];
273
274   if (RNA_struct_property_is_set(op->ptr, "location")) {
275     RNA_int_get_array(op->ptr, "location", loc);
276     paintcurve_point_add(C, op, loc);
277     return OPERATOR_FINISHED;
278   }
279
280   return OPERATOR_CANCELLED;
281 }
282
283 void PAINTCURVE_OT_add_point(wmOperatorType *ot)
284 {
285   /* identifiers */
286   ot->name = "Add New Paint Curve Point";
287   ot->description = ot->name;
288   ot->idname = "PAINTCURVE_OT_add_point";
289
290   /* api callbacks */
291   ot->invoke = paintcurve_add_point_invoke;
292   ot->exec = paintcurve_add_point_exec;
293   ot->poll = paint_curve_poll;
294
295   /* flags */
296   ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
297
298   /* properties */
299   RNA_def_int_vector(ot->srna,
300                      "location",
301                      2,
302                      NULL,
303                      0,
304                      SHRT_MAX,
305                      "Location",
306                      "Location of vertex in area space",
307                      0,
308                      SHRT_MAX);
309 }
310
311 static int paintcurve_delete_point_exec(bContext *C, wmOperator *op)
312 {
313   Paint *p = BKE_paint_get_active_from_context(C);
314   Brush *br = p->brush;
315   PaintCurve *pc;
316   PaintCurvePoint *pcp;
317   wmWindow *window = CTX_wm_window(C);
318   ARegion *ar = CTX_wm_region(C);
319   int i;
320   int tot_del = 0;
321   pc = br->paint_curve;
322
323   if (!pc || pc->tot_points == 0) {
324     return OPERATOR_CANCELLED;
325   }
326
327   ED_paintcurve_undo_push_begin(op->type->name);
328
329 #define DELETE_TAG 2
330
331   for (i = 0, pcp = pc->points; i < pc->tot_points; i++, pcp++) {
332     if (BEZT_ISSEL_ANY(&pcp->bez)) {
333       pcp->bez.f2 |= DELETE_TAG;
334       tot_del++;
335     }
336   }
337
338   if (tot_del > 0) {
339     int j = 0;
340     int new_tot = pc->tot_points - tot_del;
341     PaintCurvePoint *points_new = NULL;
342     if (new_tot > 0) {
343       points_new = MEM_mallocN(new_tot * sizeof(PaintCurvePoint), "PaintCurvePoint");
344     }
345
346     for (i = 0, pcp = pc->points; i < pc->tot_points; i++, pcp++) {
347       if (!(pcp->bez.f2 & DELETE_TAG)) {
348         points_new[j] = pc->points[i];
349
350         if ((i + 1) == pc->add_index) {
351           BKE_paint_curve_clamp_endpoint_add_index(pc, j);
352         }
353         j++;
354       }
355       else if ((i + 1) == pc->add_index) {
356         /* prefer previous point */
357         pc->add_index = j;
358       }
359     }
360     MEM_freeN(pc->points);
361
362     pc->points = points_new;
363     pc->tot_points = new_tot;
364   }
365
366 #undef DELETE_TAG
367
368   ED_paintcurve_undo_push_end();
369
370   WM_paint_cursor_tag_redraw(window, ar);
371
372   return OPERATOR_FINISHED;
373 }
374
375 void PAINTCURVE_OT_delete_point(wmOperatorType *ot)
376 {
377   /* identifiers */
378   ot->name = "Remove Paint Curve Point";
379   ot->description = ot->name;
380   ot->idname = "PAINTCURVE_OT_delete_point";
381
382   /* api callbacks */
383   ot->exec = paintcurve_delete_point_exec;
384   ot->poll = paint_curve_poll;
385
386   /* flags */
387   ot->flag = OPTYPE_UNDO;
388 }
389
390 static bool paintcurve_point_select(
391     bContext *C, wmOperator *op, const int loc[2], bool toggle, bool extend)
392 {
393   wmWindow *window = CTX_wm_window(C);
394   ARegion *ar = CTX_wm_region(C);
395   Paint *p = BKE_paint_get_active_from_context(C);
396   Brush *br = p->brush;
397   PaintCurve *pc;
398   int i;
399   const float loc_fl[2] = {UNPACK2(loc)};
400
401   pc = br->paint_curve;
402
403   if (!pc) {
404     return false;
405   }
406
407   ED_paintcurve_undo_push_begin(op->type->name);
408
409   if (toggle) {
410     PaintCurvePoint *pcp;
411     char select = 0;
412     bool selected = false;
413
414     pcp = pc->points;
415
416     for (i = 0; i < pc->tot_points; i++) {
417       if (pcp[i].bez.f1 || pcp[i].bez.f2 || pcp[i].bez.f3) {
418         selected = true;
419         break;
420       }
421     }
422
423     if (!selected) {
424       select = SELECT;
425     }
426
427     for (i = 0; i < pc->tot_points; i++) {
428       pc->points[i].bez.f1 = pc->points[i].bez.f2 = pc->points[i].bez.f3 = select;
429     }
430   }
431   else {
432     PaintCurvePoint *pcp;
433     char selflag;
434
435     pcp = paintcurve_point_get_closest(pc, loc_fl, false, PAINT_CURVE_SELECT_THRESHOLD, &selflag);
436
437     if (pcp) {
438       BKE_paint_curve_clamp_endpoint_add_index(pc, pcp - pc->points);
439
440       if (selflag == SEL_F2) {
441         if (extend) {
442           pcp->bez.f2 ^= SELECT;
443         }
444         else {
445           pcp->bez.f2 |= SELECT;
446         }
447       }
448       else if (selflag == SEL_F1) {
449         if (extend) {
450           pcp->bez.f1 ^= SELECT;
451         }
452         else {
453           pcp->bez.f1 |= SELECT;
454         }
455       }
456       else if (selflag == SEL_F3) {
457         if (extend) {
458           pcp->bez.f3 ^= SELECT;
459         }
460         else {
461           pcp->bez.f3 |= SELECT;
462         }
463       }
464     }
465
466     /* clear selection for unselected points if not extending and if a point has been selected */
467     if (!extend && pcp) {
468       for (i = 0; i < pc->tot_points; i++) {
469         pc->points[i].bez.f1 = pc->points[i].bez.f2 = pc->points[i].bez.f3 = 0;
470
471         if ((pc->points + i) == pcp) {
472           char index = paintcurve_point_co_index(selflag);
473           PAINT_CURVE_POINT_SELECT(pcp, index);
474         }
475       }
476     }
477
478     if (!pcp) {
479       ED_paintcurve_undo_push_end();
480       return false;
481     }
482   }
483
484   ED_paintcurve_undo_push_end();
485
486   WM_paint_cursor_tag_redraw(window, ar);
487
488   return true;
489 }
490
491 static int paintcurve_select_point_invoke(bContext *C, wmOperator *op, const wmEvent *event)
492 {
493   int loc[2] = {UNPACK2(event->mval)};
494   bool toggle = RNA_boolean_get(op->ptr, "toggle");
495   bool extend = RNA_boolean_get(op->ptr, "extend");
496   if (paintcurve_point_select(C, op, loc, toggle, extend)) {
497     RNA_int_set_array(op->ptr, "location", loc);
498     return OPERATOR_FINISHED;
499   }
500   else {
501     return OPERATOR_CANCELLED;
502   }
503 }
504
505 static int paintcurve_select_point_exec(bContext *C, wmOperator *op)
506 {
507   int loc[2];
508
509   if (RNA_struct_property_is_set(op->ptr, "location")) {
510     bool toggle = RNA_boolean_get(op->ptr, "toggle");
511     bool extend = RNA_boolean_get(op->ptr, "extend");
512     RNA_int_get_array(op->ptr, "location", loc);
513     if (paintcurve_point_select(C, op, loc, toggle, extend)) {
514       return OPERATOR_FINISHED;
515     }
516   }
517
518   return OPERATOR_CANCELLED;
519 }
520
521 void PAINTCURVE_OT_select(wmOperatorType *ot)
522 {
523   PropertyRNA *prop;
524
525   /* identifiers */
526   ot->name = "Select Paint Curve Point";
527   ot->description = "Select a paint curve point";
528   ot->idname = "PAINTCURVE_OT_select";
529
530   /* api callbacks */
531   ot->invoke = paintcurve_select_point_invoke;
532   ot->exec = paintcurve_select_point_exec;
533   ot->poll = paint_curve_poll;
534
535   /* flags */
536   ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
537
538   /* properties */
539   RNA_def_int_vector(ot->srna,
540                      "location",
541                      2,
542                      NULL,
543                      0,
544                      SHRT_MAX,
545                      "Location",
546                      "Location of vertex in area space",
547                      0,
548                      SHRT_MAX);
549   prop = RNA_def_boolean(ot->srna, "toggle", false, "Toggle", "(De)select all");
550   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
551   prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection");
552   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
553 }
554
555 typedef struct PointSlideData {
556   PaintCurvePoint *pcp;
557   char select;
558   int initial_loc[2];
559   float point_initial_loc[3][2];
560   int event;
561   bool align;
562 } PointSlideData;
563
564 static int paintcurve_slide_invoke(bContext *C, wmOperator *op, const wmEvent *event)
565 {
566   Paint *p = BKE_paint_get_active_from_context(C);
567   const float loc_fl[2] = {UNPACK2(event->mval)};
568   char select;
569   int i;
570   bool do_select = RNA_boolean_get(op->ptr, "select");
571   bool align = RNA_boolean_get(op->ptr, "align");
572   Brush *br = p->brush;
573   PaintCurve *pc = br->paint_curve;
574   PaintCurvePoint *pcp;
575
576   if (!pc) {
577     return OPERATOR_PASS_THROUGH;
578   }
579
580   if (do_select) {
581     pcp = paintcurve_point_get_closest(pc, loc_fl, align, PAINT_CURVE_SELECT_THRESHOLD, &select);
582   }
583   else {
584     pcp = NULL;
585     /* just find first selected point */
586     for (i = 0; i < pc->tot_points; i++) {
587       if ((select = paintcurve_point_side_index(&pc->points[i].bez, i == 0, SEL_F3))) {
588         pcp = &pc->points[i];
589         break;
590       }
591     }
592   }
593
594   if (pcp) {
595     ARegion *ar = CTX_wm_region(C);
596     wmWindow *window = CTX_wm_window(C);
597     PointSlideData *psd = MEM_mallocN(sizeof(PointSlideData), "PointSlideData");
598     copy_v2_v2_int(psd->initial_loc, event->mval);
599     psd->event = event->type;
600     psd->pcp = pcp;
601     psd->select = paintcurve_point_co_index(select);
602     for (i = 0; i < 3; i++) {
603       copy_v2_v2(psd->point_initial_loc[i], pcp->bez.vec[i]);
604     }
605     psd->align = align;
606     op->customdata = psd;
607
608     /* first, clear all selection from points */
609     for (i = 0; i < pc->tot_points; i++) {
610       pc->points[i].bez.f1 = pc->points[i].bez.f3 = pc->points[i].bez.f2 = 0;
611     }
612
613     /* only select the active point */
614     PAINT_CURVE_POINT_SELECT(pcp, psd->select);
615     BKE_paint_curve_clamp_endpoint_add_index(pc, pcp - pc->points);
616
617     WM_event_add_modal_handler(C, op);
618     WM_paint_cursor_tag_redraw(window, ar);
619     return OPERATOR_RUNNING_MODAL;
620   }
621
622   return OPERATOR_PASS_THROUGH;
623 }
624
625 static int paintcurve_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
626 {
627   PointSlideData *psd = op->customdata;
628
629   if (event->type == psd->event && event->val == KM_RELEASE) {
630     MEM_freeN(psd);
631     ED_paintcurve_undo_push_begin(op->type->name);
632     ED_paintcurve_undo_push_end();
633     return OPERATOR_FINISHED;
634   }
635
636   switch (event->type) {
637     case MOUSEMOVE: {
638       ARegion *ar = CTX_wm_region(C);
639       wmWindow *window = CTX_wm_window(C);
640       float diff[2] = {event->mval[0] - psd->initial_loc[0], event->mval[1] - psd->initial_loc[1]};
641       if (psd->select == 1) {
642         int i;
643         for (i = 0; i < 3; i++) {
644           add_v2_v2v2(psd->pcp->bez.vec[i], diff, psd->point_initial_loc[i]);
645         }
646       }
647       else {
648         add_v2_v2(diff, psd->point_initial_loc[psd->select]);
649         copy_v2_v2(psd->pcp->bez.vec[psd->select], diff);
650
651         if (psd->align) {
652           char opposite = (psd->select == 0) ? 2 : 0;
653           sub_v2_v2v2(diff, psd->pcp->bez.vec[1], psd->pcp->bez.vec[psd->select]);
654           add_v2_v2v2(psd->pcp->bez.vec[opposite], psd->pcp->bez.vec[1], diff);
655         }
656       }
657       WM_paint_cursor_tag_redraw(window, ar);
658       break;
659     }
660     default:
661       break;
662   }
663
664   return OPERATOR_RUNNING_MODAL;
665 }
666
667 void PAINTCURVE_OT_slide(wmOperatorType *ot)
668 {
669   /* identifiers */
670   ot->name = "Slide Paint Curve Point";
671   ot->description = "Select and slide paint curve point";
672   ot->idname = "PAINTCURVE_OT_slide";
673
674   /* api callbacks */
675   ot->invoke = paintcurve_slide_invoke;
676   ot->modal = paintcurve_slide_modal;
677   ot->poll = paint_curve_poll;
678
679   /* flags */
680   ot->flag = OPTYPE_UNDO;
681
682   /* properties */
683   RNA_def_boolean(
684       ot->srna, "align", false, "Align Handles", "Aligns opposite point handle during transform");
685   RNA_def_boolean(
686       ot->srna, "select", true, "Select", "Attempt to select a point handle before transform");
687 }
688
689 static int paintcurve_draw_exec(bContext *C, wmOperator *UNUSED(op))
690 {
691   ePaintMode mode = BKE_paintmode_get_active_from_context(C);
692   const char *name;
693
694   switch (mode) {
695     case PAINT_MODE_TEXTURE_2D:
696     case PAINT_MODE_TEXTURE_3D:
697       name = "PAINT_OT_image_paint";
698       break;
699     case PAINT_MODE_WEIGHT:
700       name = "PAINT_OT_weight_paint";
701       break;
702     case PAINT_MODE_VERTEX:
703       name = "PAINT_OT_vertex_paint";
704       break;
705     case PAINT_MODE_SCULPT:
706       name = "SCULPT_OT_brush_stroke";
707       break;
708     default:
709       return OPERATOR_PASS_THROUGH;
710   }
711
712   return WM_operator_name_call(C, name, WM_OP_INVOKE_DEFAULT, NULL);
713 }
714
715 void PAINTCURVE_OT_draw(wmOperatorType *ot)
716 {
717   /* identifiers */
718   ot->name = "Draw Curve";
719   ot->description = "Draw curve";
720   ot->idname = "PAINTCURVE_OT_draw";
721
722   /* api callbacks */
723   ot->exec = paintcurve_draw_exec;
724   ot->poll = paint_curve_poll;
725
726   /* flags */
727   ot->flag = OPTYPE_UNDO;
728 }
729
730 static int paintcurve_cursor_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
731 {
732   ePaintMode mode = BKE_paintmode_get_active_from_context(C);
733
734   switch (mode) {
735     case PAINT_MODE_TEXTURE_2D: {
736       ARegion *ar = CTX_wm_region(C);
737       SpaceImage *sima = CTX_wm_space_image(C);
738       float location[2];
739
740       if (!sima) {
741         return OPERATOR_CANCELLED;
742       }
743
744       UI_view2d_region_to_view(
745           &ar->v2d, event->mval[0], event->mval[1], &location[0], &location[1]);
746       copy_v2_v2(sima->cursor, location);
747       WM_event_add_notifier(C, NC_SPACE | ND_SPACE_IMAGE, NULL);
748       break;
749     }
750     default:
751       ED_view3d_cursor3d_update(C, event->mval, true, V3D_CURSOR_ORIENT_VIEW);
752       break;
753   }
754
755   return OPERATOR_FINISHED;
756 }
757
758 void PAINTCURVE_OT_cursor(wmOperatorType *ot)
759 {
760   /* identifiers */
761   ot->name = "Place Cursor";
762   ot->description = "Place cursor";
763   ot->idname = "PAINTCURVE_OT_cursor";
764
765   /* api callbacks */
766   ot->invoke = paintcurve_cursor_invoke;
767   ot->poll = paint_curve_poll;
768
769   /* flags */
770   ot->flag = 0;
771 }