Cleanup: comment line length (windowmanager)
[blender.git] / source / blender / windowmanager / gizmo / intern / wm_gizmo_group.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2014 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup wm
22  *
23  * \name Gizmo-Group
24  *
25  * Gizmo-groups store and manage groups of gizmos. They can be
26  * attached to modal handlers and have own keymaps.
27  */
28
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "MEM_guardedalloc.h"
33
34 #include "BLI_listbase.h"
35 #include "BLI_string.h"
36 #include "BLI_math.h"
37
38 #include "BKE_context.h"
39 #include "BKE_main.h"
40 #include "BKE_report.h"
41 #include "BKE_workspace.h"
42
43 #include "RNA_access.h"
44 #include "RNA_define.h"
45
46 #include "WM_api.h"
47 #include "WM_types.h"
48 #include "wm_event_system.h"
49
50 #include "ED_screen.h"
51 #include "ED_undo.h"
52
53 /* own includes */
54 #include "wm_gizmo_wmapi.h"
55 #include "wm_gizmo_intern.h"
56
57 #ifdef WITH_PYTHON
58 #  include "BPY_extern.h"
59 #endif
60
61 /* Allow gizmo part's to be single click only,
62  * dragging falls back to activating their 'drag_part' action. */
63 #define USE_DRAG_DETECT
64
65 /* -------------------------------------------------------------------- */
66 /** \name wmGizmoGroup
67  *
68  * \{ */
69
70 /**
71  * Create a new gizmo-group from \a gzgt.
72  */
73 wmGizmoGroup *wm_gizmogroup_new_from_type(wmGizmoMap *gzmap, wmGizmoGroupType *gzgt)
74 {
75   wmGizmoGroup *gzgroup = MEM_callocN(sizeof(*gzgroup), "gizmo-group");
76   gzgroup->type = gzgt;
77
78   /* keep back-link */
79   gzgroup->parent_gzmap = gzmap;
80
81   BLI_addtail(&gzmap->groups, gzgroup);
82
83   return gzgroup;
84 }
85
86 void wm_gizmogroup_free(bContext *C, wmGizmoGroup *gzgroup)
87 {
88   wmGizmoMap *gzmap = gzgroup->parent_gzmap;
89
90   /* Similar to WM_gizmo_unlink, but only to keep gzmap state correct,
91    * we don't want to run callbacks. */
92   if (gzmap->gzmap_context.highlight &&
93       gzmap->gzmap_context.highlight->parent_gzgroup == gzgroup) {
94     wm_gizmomap_highlight_set(gzmap, C, NULL, 0);
95   }
96   if (gzmap->gzmap_context.modal && gzmap->gzmap_context.modal->parent_gzgroup == gzgroup) {
97     wm_gizmomap_modal_set(gzmap, C, gzmap->gzmap_context.modal, NULL, false);
98   }
99
100   for (wmGizmo *gz = gzgroup->gizmos.first, *gz_next; gz; gz = gz_next) {
101     gz_next = gz->next;
102     if (gzmap->gzmap_context.select.len) {
103       WM_gizmo_select_unlink(gzmap, gz);
104     }
105     WM_gizmo_free(gz);
106   }
107   BLI_listbase_clear(&gzgroup->gizmos);
108
109 #ifdef WITH_PYTHON
110   if (gzgroup->py_instance) {
111     /* do this first in case there are any __del__ functions or
112      * similar that use properties */
113     BPY_DECREF_RNA_INVALIDATE(gzgroup->py_instance);
114   }
115 #endif
116
117   if (gzgroup->reports && (gzgroup->reports->flag & RPT_FREE)) {
118     BKE_reports_clear(gzgroup->reports);
119     MEM_freeN(gzgroup->reports);
120   }
121
122   if (gzgroup->customdata_free) {
123     gzgroup->customdata_free(gzgroup->customdata);
124   }
125   else {
126     MEM_SAFE_FREE(gzgroup->customdata);
127   }
128
129   BLI_remlink(&gzmap->groups, gzgroup);
130
131   MEM_freeN(gzgroup);
132 }
133
134 /**
135  * Add \a gizmo to \a gzgroup and make sure its name is unique within the group.
136  */
137 void wm_gizmogroup_gizmo_register(wmGizmoGroup *gzgroup, wmGizmo *gz)
138 {
139   BLI_assert(BLI_findindex(&gzgroup->gizmos, gz) == -1);
140   BLI_addtail(&gzgroup->gizmos, gz);
141   gz->parent_gzgroup = gzgroup;
142 }
143
144 int WM_gizmo_cmp_temp_fl(const void *gz_a_ptr, const void *gz_b_ptr)
145 {
146   const wmGizmo *gz_a = gz_a_ptr;
147   const wmGizmo *gz_b = gz_b_ptr;
148   if (gz_a->temp.f < gz_b->temp.f) {
149     return -1;
150   }
151   else if (gz_a->temp.f > gz_b->temp.f) {
152     return 1;
153   }
154   else {
155     return 0;
156   }
157 }
158
159 int WM_gizmo_cmp_temp_fl_reverse(const void *gz_a_ptr, const void *gz_b_ptr)
160 {
161   const wmGizmo *gz_a = gz_a_ptr;
162   const wmGizmo *gz_b = gz_b_ptr;
163   if (gz_a->temp.f < gz_b->temp.f) {
164     return 1;
165   }
166   else if (gz_a->temp.f > gz_b->temp.f) {
167     return -1;
168   }
169   else {
170     return 0;
171   }
172 }
173
174 wmGizmo *wm_gizmogroup_find_intersected_gizmo(const wmGizmoGroup *gzgroup,
175                                               bContext *C,
176                                               const wmEvent *event,
177                                               int *r_part)
178 {
179   for (wmGizmo *gz = gzgroup->gizmos.first; gz; gz = gz->next) {
180     if (gz->type->test_select && (gz->flag & (WM_GIZMO_HIDDEN | WM_GIZMO_HIDDEN_SELECT)) == 0) {
181       if ((*r_part = gz->type->test_select(C, gz, event->mval)) != -1) {
182         return gz;
183       }
184     }
185   }
186
187   return NULL;
188 }
189
190 /**
191  * Adds all gizmos of \a gzgroup that can be selected to the head of \a listbase.
192  * Added items need freeing!
193  */
194 void wm_gizmogroup_intersectable_gizmos_to_list(const wmGizmoGroup *gzgroup, ListBase *listbase)
195 {
196   for (wmGizmo *gz = gzgroup->gizmos.first; gz; gz = gz->next) {
197     if ((gz->flag & (WM_GIZMO_HIDDEN | WM_GIZMO_HIDDEN_SELECT)) == 0) {
198       if (((gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) &&
199            (gz->type->draw_select || gz->type->test_select)) ||
200           ((gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) == 0 && gz->type->test_select)) {
201         BLI_addhead(listbase, BLI_genericNodeN(gz));
202       }
203     }
204   }
205 }
206
207 void WM_gizmogroup_ensure_init(const bContext *C, wmGizmoGroup *gzgroup)
208 {
209   /* prepare for first draw */
210   if (UNLIKELY((gzgroup->init_flag & WM_GIZMOGROUP_INIT_SETUP) == 0)) {
211     gzgroup->type->setup(C, gzgroup);
212
213     /* Not ideal, initialize keymap here, needed for RNA runtime generated gizmos. */
214     wmGizmoGroupType *gzgt = gzgroup->type;
215     if (gzgt->keymap == NULL) {
216       wmWindowManager *wm = CTX_wm_manager(C);
217       wm_gizmogrouptype_setup_keymap(gzgt, wm->defaultconf);
218       BLI_assert(gzgt->keymap != NULL);
219     }
220     gzgroup->init_flag |= WM_GIZMOGROUP_INIT_SETUP;
221   }
222
223   /* Refresh may be called multiple times,
224    * this just ensures its called at least once before we draw. */
225   if (UNLIKELY((gzgroup->init_flag & WM_GIZMOGROUP_INIT_REFRESH) == 0)) {
226     if (gzgroup->type->refresh) {
227       gzgroup->type->refresh(C, gzgroup);
228     }
229     gzgroup->init_flag |= WM_GIZMOGROUP_INIT_REFRESH;
230   }
231 }
232
233 bool WM_gizmo_group_type_poll(const bContext *C, const struct wmGizmoGroupType *gzgt)
234 {
235   /* If we're tagged, only use compatible. */
236   if (gzgt->owner_id[0] != '\0') {
237     const WorkSpace *workspace = CTX_wm_workspace(C);
238     if (BKE_workspace_owner_id_check(workspace, gzgt->owner_id) == false) {
239       return false;
240     }
241   }
242   /* Check for poll function, if gizmo-group belongs to an operator,
243    * also check if the operator is running. */
244   return (!gzgt->poll || gzgt->poll(C, (wmGizmoGroupType *)gzgt));
245 }
246
247 bool wm_gizmogroup_is_visible_in_drawstep(const wmGizmoGroup *gzgroup,
248                                           const eWM_GizmoFlagMapDrawStep drawstep)
249 {
250   switch (drawstep) {
251     case WM_GIZMOMAP_DRAWSTEP_2D:
252       return (gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) == 0;
253     case WM_GIZMOMAP_DRAWSTEP_3D:
254       return (gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D);
255     default:
256       BLI_assert(0);
257       return false;
258   }
259 }
260
261 bool wm_gizmogroup_is_any_selected(const wmGizmoGroup *gzgroup)
262 {
263   if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_SELECT) {
264     for (const wmGizmo *gz = gzgroup->gizmos.first; gz; gz = gz->next) {
265       if (gz->state & WM_GIZMO_STATE_SELECT) {
266         return true;
267       }
268     }
269   }
270   return false;
271 }
272
273 /** \} */
274
275 /** \name Gizmo operators
276  *
277  * Basic operators for gizmo interaction with user configurable keymaps.
278  *
279  * \{ */
280
281 static int gizmo_select_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
282 {
283   ARegion *ar = CTX_wm_region(C);
284   wmGizmoMap *gzmap = ar->gizmo_map;
285   wmGizmoMapSelectState *msel = &gzmap->gzmap_context.select;
286   wmGizmo *highlight = gzmap->gzmap_context.highlight;
287
288   bool extend = RNA_boolean_get(op->ptr, "extend");
289   bool deselect = RNA_boolean_get(op->ptr, "deselect");
290   bool toggle = RNA_boolean_get(op->ptr, "toggle");
291
292   /* deselect all first */
293   if (extend == false && deselect == false && toggle == false) {
294     wm_gizmomap_deselect_all(gzmap);
295     BLI_assert(msel->items == NULL && msel->len == 0);
296     UNUSED_VARS_NDEBUG(msel);
297   }
298
299   if (highlight) {
300     const bool is_selected = (highlight->state & WM_GIZMO_STATE_SELECT);
301     bool redraw = false;
302
303     if (toggle) {
304       /* toggle: deselect if already selected, else select */
305       deselect = is_selected;
306     }
307
308     if (deselect) {
309       if (is_selected && WM_gizmo_select_set(gzmap, highlight, false)) {
310         redraw = true;
311       }
312     }
313     else if (wm_gizmo_select_and_highlight(C, gzmap, highlight)) {
314       redraw = true;
315     }
316
317     if (redraw) {
318       ED_region_tag_redraw(ar);
319     }
320
321     return OPERATOR_FINISHED;
322   }
323   else {
324     BLI_assert(0);
325     return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
326   }
327 }
328
329 void GIZMOGROUP_OT_gizmo_select(wmOperatorType *ot)
330 {
331   /* identifiers */
332   ot->name = "Gizmo Select";
333   ot->description = "Select the currently highlighted gizmo";
334   ot->idname = "GIZMOGROUP_OT_gizmo_select";
335
336   /* api callbacks */
337   ot->invoke = gizmo_select_invoke;
338
339   ot->flag = OPTYPE_UNDO;
340
341   WM_operator_properties_mouse_select(ot);
342 }
343
344 typedef struct GizmoTweakData {
345   wmGizmoMap *gzmap;
346   wmGizmoGroup *gzgroup;
347   wmGizmo *gz_modal;
348
349   int init_event; /* initial event type */
350   int flag;       /* tweak flags */
351
352 #ifdef USE_DRAG_DETECT
353   /* True until the mouse is moved (only use when the operator has no modal).
354    * this allows some gizmos to be click-only. */
355   enum {
356     /* Don't detect dragging. */
357     DRAG_NOP = 0,
358     /* Detect dragging (wait until a drag or click is detected). */
359     DRAG_DETECT,
360     /* Drag has started, idle until there is no active modal operator.
361      * This is needed because finishing the modal operator also exits
362      * the modal gizmo state (un-grabbs the cursor).
363      * Ideally this workaround could be removed later. */
364     DRAG_IDLE,
365   } drag_state;
366 #endif
367
368 } GizmoTweakData;
369
370 static bool gizmo_tweak_start(bContext *C, wmGizmoMap *gzmap, wmGizmo *gz, const wmEvent *event)
371 {
372   /* activate highlighted gizmo */
373   wm_gizmomap_modal_set(gzmap, C, gz, event, true);
374
375   return (gz->state & WM_GIZMO_STATE_MODAL);
376 }
377
378 static bool gizmo_tweak_start_and_finish(
379     bContext *C, wmGizmoMap *gzmap, wmGizmo *gz, const wmEvent *event, bool *r_is_modal)
380 {
381   wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, gz->highlight_part);
382   if (r_is_modal) {
383     *r_is_modal = false;
384   }
385   if (gzop && gzop->type) {
386
387     /* Undo/Redo */
388     if (gzop->is_redo) {
389       wmWindowManager *wm = CTX_wm_manager(C);
390       wmOperator *op = WM_operator_last_redo(C);
391
392       /* We may want to enable this, for now the gizmo can manage it's own properties. */
393 #if 0
394       IDP_MergeGroup(gzop->ptr.data, op->properties, false);
395 #endif
396
397       WM_operator_free_all_after(wm, op);
398       ED_undo_pop_op(C, op);
399     }
400
401     /* XXX temporary workaround for modal gizmo operator
402      * conflicting with modal operator attached to gizmo */
403     if (gzop->type->modal) {
404       /* activate highlighted gizmo */
405       wm_gizmomap_modal_set(gzmap, C, gz, event, true);
406       if (r_is_modal) {
407         *r_is_modal = true;
408       }
409     }
410     else {
411       if (gz->parent_gzgroup->type->invoke_prepare) {
412         gz->parent_gzgroup->type->invoke_prepare(C, gz->parent_gzgroup, gz);
413       }
414       /* Allow for 'button' gizmos, single click to run an action. */
415       WM_gizmo_operator_invoke(C, gz, gzop);
416     }
417     return true;
418   }
419   else {
420     return false;
421   }
422 }
423
424 static void gizmo_tweak_finish(bContext *C, wmOperator *op, const bool cancel, bool clear_modal)
425 {
426   GizmoTweakData *mtweak = op->customdata;
427   if (mtweak->gz_modal->type->exit) {
428     mtweak->gz_modal->type->exit(C, mtweak->gz_modal, cancel);
429   }
430   if (clear_modal) {
431     /* The gizmo may have been removed. */
432     if ((BLI_findindex(&mtweak->gzmap->groups, mtweak->gzgroup) != -1) &&
433         (BLI_findindex(&mtweak->gzgroup->gizmos, mtweak->gz_modal) != -1)) {
434       wm_gizmomap_modal_set(mtweak->gzmap, C, mtweak->gz_modal, NULL, false);
435     }
436   }
437   MEM_freeN(mtweak);
438 }
439
440 static int gizmo_tweak_modal(bContext *C, wmOperator *op, const wmEvent *event)
441 {
442   GizmoTweakData *mtweak = op->customdata;
443   wmGizmo *gz = mtweak->gz_modal;
444   int retval = OPERATOR_PASS_THROUGH;
445   bool clear_modal = true;
446
447   if (gz == NULL) {
448     BLI_assert(0);
449     return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
450   }
451
452 #ifdef USE_DRAG_DETECT
453   wmGizmoMap *gzmap = mtweak->gzmap;
454   if (mtweak->drag_state == DRAG_DETECT) {
455     if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
456       if (len_manhattan_v2v2_int(&event->x, gzmap->gzmap_context.event_xy) >=
457           WM_EVENT_CURSOR_CLICK_DRAG_THRESHOLD) {
458         mtweak->drag_state = DRAG_IDLE;
459         gz->highlight_part = gz->drag_part;
460       }
461     }
462     else if (event->type == mtweak->init_event && event->val == KM_RELEASE) {
463       mtweak->drag_state = DRAG_NOP;
464       retval = OPERATOR_FINISHED;
465     }
466
467     if (mtweak->drag_state != DRAG_DETECT) {
468       /* Follow logic in 'gizmo_tweak_invoke' */
469       bool is_modal = false;
470       if (gizmo_tweak_start_and_finish(C, gzmap, gz, event, &is_modal)) {
471         if (is_modal) {
472           clear_modal = false;
473         }
474       }
475       else {
476         if (!gizmo_tweak_start(C, gzmap, gz, event)) {
477           retval = OPERATOR_FINISHED;
478         }
479       }
480     }
481   }
482   if (mtweak->drag_state == DRAG_IDLE) {
483     if (gzmap->gzmap_context.modal != NULL) {
484       return OPERATOR_PASS_THROUGH;
485     }
486     else {
487       gizmo_tweak_finish(C, op, false, false);
488       return OPERATOR_FINISHED;
489     }
490   }
491 #endif /* USE_DRAG_DETECT */
492
493   if (retval == OPERATOR_FINISHED) {
494     /* pass */
495   }
496   else if (event->type == mtweak->init_event && event->val == KM_RELEASE) {
497     retval = OPERATOR_FINISHED;
498   }
499   else if (event->type == EVT_MODAL_MAP) {
500     switch (event->val) {
501       case TWEAK_MODAL_CANCEL:
502         retval = OPERATOR_CANCELLED;
503         break;
504       case TWEAK_MODAL_CONFIRM:
505         retval = OPERATOR_FINISHED;
506         break;
507       case TWEAK_MODAL_PRECISION_ON:
508         mtweak->flag |= WM_GIZMO_TWEAK_PRECISE;
509         break;
510       case TWEAK_MODAL_PRECISION_OFF:
511         mtweak->flag &= ~WM_GIZMO_TWEAK_PRECISE;
512         break;
513
514       case TWEAK_MODAL_SNAP_ON:
515         mtweak->flag |= WM_GIZMO_TWEAK_SNAP;
516         break;
517       case TWEAK_MODAL_SNAP_OFF:
518         mtweak->flag &= ~WM_GIZMO_TWEAK_SNAP;
519         break;
520     }
521   }
522
523   if (retval != OPERATOR_PASS_THROUGH) {
524     gizmo_tweak_finish(C, op, retval != OPERATOR_FINISHED, clear_modal);
525     return retval;
526   }
527
528   /* handle gizmo */
529   wmGizmoFnModal modal_fn = gz->custom_modal ? gz->custom_modal : gz->type->modal;
530   if (modal_fn) {
531     int modal_retval = modal_fn(C, gz, event, mtweak->flag);
532
533     if ((modal_retval & OPERATOR_RUNNING_MODAL) == 0) {
534       gizmo_tweak_finish(C, op, (modal_retval & OPERATOR_CANCELLED) != 0, true);
535       return OPERATOR_FINISHED;
536     }
537
538     /* Ugly hack to send gizmo events */
539     ((wmEvent *)event)->type = EVT_GIZMO_UPDATE;
540   }
541
542   /* always return PASS_THROUGH so modal handlers
543    * with gizmos attached can update */
544   BLI_assert(retval == OPERATOR_PASS_THROUGH);
545   return OPERATOR_PASS_THROUGH;
546 }
547
548 static int gizmo_tweak_invoke(bContext *C, wmOperator *op, const wmEvent *event)
549 {
550   ARegion *ar = CTX_wm_region(C);
551   wmGizmoMap *gzmap = ar->gizmo_map;
552   wmGizmo *gz = gzmap->gzmap_context.highlight;
553
554   /* Needed for single click actions which don't enter modal state. */
555   WM_tooltip_clear(C, CTX_wm_window(C));
556
557   if (!gz) {
558     /* wm_handlers_do_intern shouldn't let this happen */
559     BLI_assert(0);
560     return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
561   }
562
563   bool use_drag_fallback = false;
564
565 #ifdef USE_DRAG_DETECT
566   use_drag_fallback = !ELEM(gz->drag_part, -1, gz->highlight_part);
567 #endif
568
569   if (use_drag_fallback == false) {
570     if (gizmo_tweak_start_and_finish(C, gzmap, gz, event, NULL)) {
571       return OPERATOR_FINISHED;
572     }
573   }
574
575   bool use_drag_detect = false;
576 #ifdef USE_DRAG_DETECT
577   if (use_drag_fallback) {
578     wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, gz->highlight_part);
579     if (gzop && gzop->type) {
580       if (gzop->type->modal == NULL) {
581         use_drag_detect = true;
582       }
583     }
584   }
585 #endif
586
587   if (use_drag_detect == false) {
588     if (!gizmo_tweak_start(C, gzmap, gz, event)) {
589       /* failed to start */
590       return OPERATOR_PASS_THROUGH;
591     }
592   }
593
594   GizmoTweakData *mtweak = MEM_mallocN(sizeof(GizmoTweakData), __func__);
595
596   mtweak->init_event = WM_userdef_event_type_from_keymap_type(event->type);
597   mtweak->gz_modal = gzmap->gzmap_context.highlight;
598   mtweak->gzgroup = mtweak->gz_modal->parent_gzgroup;
599   mtweak->gzmap = gzmap;
600   mtweak->flag = 0;
601
602 #ifdef USE_DRAG_DETECT
603   mtweak->drag_state = use_drag_detect ? DRAG_DETECT : DRAG_NOP;
604 #endif
605
606   op->customdata = mtweak;
607
608   WM_event_add_modal_handler(C, op);
609
610   return OPERATOR_RUNNING_MODAL;
611 }
612
613 void GIZMOGROUP_OT_gizmo_tweak(wmOperatorType *ot)
614 {
615   /* identifiers */
616   ot->name = "Gizmo Tweak";
617   ot->description = "Tweak the active gizmo";
618   ot->idname = "GIZMOGROUP_OT_gizmo_tweak";
619
620   /* api callbacks */
621   ot->invoke = gizmo_tweak_invoke;
622   ot->modal = gizmo_tweak_modal;
623
624   /* TODO(campbell) This causes problems tweaking settings for operators,
625    * need to find a way to support this. */
626 #if 0
627   ot->flag = OPTYPE_UNDO;
628 #endif
629 }
630
631 /** \} */
632
633 static wmKeyMap *gizmogroup_tweak_modal_keymap(wmKeyConfig *keyconf, const char *gzgroupname)
634 {
635   wmKeyMap *keymap;
636   char name[KMAP_MAX_NAME];
637
638   static EnumPropertyItem modal_items[] = {
639       {TWEAK_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
640       {TWEAK_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
641       {TWEAK_MODAL_PRECISION_ON, "PRECISION_ON", 0, "Enable Precision", ""},
642       {TWEAK_MODAL_PRECISION_OFF, "PRECISION_OFF", 0, "Disable Precision", ""},
643       {TWEAK_MODAL_SNAP_ON, "SNAP_ON", 0, "Enable Snap", ""},
644       {TWEAK_MODAL_SNAP_OFF, "SNAP_OFF", 0, "Disable Snap", ""},
645       {0, NULL, 0, NULL, NULL},
646   };
647
648   BLI_snprintf(name, sizeof(name), "%s Tweak Modal Map", gzgroupname);
649   keymap = WM_modalkeymap_get(keyconf, name);
650
651   /* this function is called for each spacetype, only needs to add map once */
652   if (keymap && keymap->modal_items) {
653     return NULL;
654   }
655
656   keymap = WM_modalkeymap_add(keyconf, name, modal_items);
657
658   /* items for modal map */
659   WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CANCEL);
660   WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CANCEL);
661
662   WM_modalkeymap_add_item(keymap, RETKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CONFIRM);
663   WM_modalkeymap_add_item(keymap, PADENTER, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_CONFIRM);
664
665   WM_modalkeymap_add_item(keymap, RIGHTSHIFTKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_PRECISION_ON);
666   WM_modalkeymap_add_item(keymap, RIGHTSHIFTKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_PRECISION_OFF);
667   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_PRECISION_ON);
668   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_PRECISION_OFF);
669
670   WM_modalkeymap_add_item(keymap, RIGHTCTRLKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_SNAP_ON);
671   WM_modalkeymap_add_item(keymap, RIGHTCTRLKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_SNAP_OFF);
672   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, TWEAK_MODAL_SNAP_ON);
673   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, TWEAK_MODAL_SNAP_OFF);
674
675   WM_modalkeymap_assign(keymap, "GIZMOGROUP_OT_gizmo_tweak");
676
677   return keymap;
678 }
679
680 /**
681  * Common default keymap for gizmo groups
682  */
683 wmKeyMap *WM_gizmogroup_keymap_common(const wmGizmoGroupType *gzgt, wmKeyConfig *config)
684 {
685   /* Use area and region id since we might have multiple gizmos
686    * with the same name in different areas/regions. */
687   wmKeyMap *km = WM_keymap_ensure(
688       config, gzgt->name, gzgt->gzmap_params.spaceid, gzgt->gzmap_params.regionid);
689
690   WM_keymap_add_item(km, "GIZMOGROUP_OT_gizmo_tweak", LEFTMOUSE, KM_PRESS, KM_ANY, 0);
691   gizmogroup_tweak_modal_keymap(config, gzgt->name);
692
693   return km;
694 }
695
696 /**
697  * Variation of #WM_gizmogroup_keymap_common but with keymap items for selection
698  */
699 wmKeyMap *WM_gizmogroup_keymap_common_select(const wmGizmoGroupType *gzgt, wmKeyConfig *config)
700 {
701   /* Use area and region id since we might have multiple gizmos
702    * with the same name in different areas/regions. */
703   wmKeyMap *km = WM_keymap_ensure(
704       config, gzgt->name, gzgt->gzmap_params.spaceid, gzgt->gzmap_params.regionid);
705   /* FIXME(campbell) */
706 #if 0
707   const int select_mouse = (U.flag & USER_LMOUSESELECT) ? LEFTMOUSE : RIGHTMOUSE;
708   const int select_tweak = (U.flag & USER_LMOUSESELECT) ? EVT_TWEAK_L : EVT_TWEAK_R;
709   const int action_mouse = (U.flag & USER_LMOUSESELECT) ? RIGHTMOUSE : LEFTMOUSE;
710 #else
711   const int select_mouse = RIGHTMOUSE;
712   const int select_tweak = EVT_TWEAK_R;
713   const int action_mouse = LEFTMOUSE;
714 #endif
715
716   WM_keymap_add_item(km, "GIZMOGROUP_OT_gizmo_tweak", action_mouse, KM_PRESS, KM_ANY, 0);
717   WM_keymap_add_item(km, "GIZMOGROUP_OT_gizmo_tweak", select_tweak, KM_ANY, 0, 0);
718   gizmogroup_tweak_modal_keymap(config, gzgt->name);
719
720   wmKeyMapItem *kmi = WM_keymap_add_item(
721       km, "GIZMOGROUP_OT_gizmo_select", select_mouse, KM_PRESS, 0, 0);
722   RNA_boolean_set(kmi->ptr, "extend", false);
723   RNA_boolean_set(kmi->ptr, "deselect", false);
724   RNA_boolean_set(kmi->ptr, "toggle", false);
725   kmi = WM_keymap_add_item(km, "GIZMOGROUP_OT_gizmo_select", select_mouse, KM_PRESS, KM_SHIFT, 0);
726   RNA_boolean_set(kmi->ptr, "extend", false);
727   RNA_boolean_set(kmi->ptr, "deselect", false);
728   RNA_boolean_set(kmi->ptr, "toggle", true);
729
730   return km;
731 }
732
733 /** \} */ /* wmGizmoGroup */
734
735 /* -------------------------------------------------------------------- */
736 /** \name wmGizmoGroupType
737  *
738  * \{ */
739
740 struct wmGizmoGroupTypeRef *WM_gizmomaptype_group_find_ptr(struct wmGizmoMapType *gzmap_type,
741                                                            const wmGizmoGroupType *gzgt)
742 {
743   /* could use hash lookups as operator types do, for now simple search. */
744   for (wmGizmoGroupTypeRef *gzgt_ref = gzmap_type->grouptype_refs.first; gzgt_ref;
745        gzgt_ref = gzgt_ref->next) {
746     if (gzgt_ref->type == gzgt) {
747       return gzgt_ref;
748     }
749   }
750   return NULL;
751 }
752
753 struct wmGizmoGroupTypeRef *WM_gizmomaptype_group_find(struct wmGizmoMapType *gzmap_type,
754                                                        const char *idname)
755 {
756   /* could use hash lookups as operator types do, for now simple search. */
757   for (wmGizmoGroupTypeRef *gzgt_ref = gzmap_type->grouptype_refs.first; gzgt_ref;
758        gzgt_ref = gzgt_ref->next) {
759     if (STREQ(idname, gzgt_ref->type->idname)) {
760       return gzgt_ref;
761     }
762   }
763   return NULL;
764 }
765
766 /**
767  * Use this for registering gizmos on startup.
768  * For runtime, use #WM_gizmomaptype_group_link_runtime.
769  */
770 wmGizmoGroupTypeRef *WM_gizmomaptype_group_link(wmGizmoMapType *gzmap_type, const char *idname)
771 {
772   wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
773   BLI_assert(gzgt != NULL);
774   return WM_gizmomaptype_group_link_ptr(gzmap_type, gzgt);
775 }
776
777 wmGizmoGroupTypeRef *WM_gizmomaptype_group_link_ptr(wmGizmoMapType *gzmap_type,
778                                                     wmGizmoGroupType *gzgt)
779 {
780   wmGizmoGroupTypeRef *gzgt_ref = MEM_callocN(sizeof(wmGizmoGroupTypeRef), "gizmo-group-ref");
781   gzgt_ref->type = gzgt;
782   BLI_addtail(&gzmap_type->grouptype_refs, gzgt_ref);
783   return gzgt_ref;
784 }
785
786 void WM_gizmomaptype_group_init_runtime_keymap(const Main *bmain, wmGizmoGroupType *gzgt)
787 {
788   /* init keymap - on startup there's an extra call to init keymaps for 'permanent' gizmo-groups */
789   wm_gizmogrouptype_setup_keymap(gzgt, ((wmWindowManager *)bmain->wm.first)->defaultconf);
790 }
791
792 void WM_gizmomaptype_group_init_runtime(const Main *bmain,
793                                         wmGizmoMapType *gzmap_type,
794                                         wmGizmoGroupType *gzgt)
795 {
796   /* Tools add themselves. */
797   if (gzgt->flag & WM_GIZMOGROUPTYPE_TOOL_INIT) {
798     return;
799   }
800
801   /* now create a gizmo for all existing areas */
802   for (bScreen *sc = bmain->screens.first; sc; sc = sc->id.next) {
803     for (ScrArea *sa = sc->areabase.first; sa; sa = sa->next) {
804       for (SpaceLink *sl = sa->spacedata.first; sl; sl = sl->next) {
805         ListBase *lb = (sl == sa->spacedata.first) ? &sa->regionbase : &sl->regionbase;
806         for (ARegion *ar = lb->first; ar; ar = ar->next) {
807           wmGizmoMap *gzmap = ar->gizmo_map;
808           if (gzmap && gzmap->type == gzmap_type) {
809             WM_gizmomaptype_group_init_runtime_with_region(gzmap_type, gzgt, ar);
810           }
811         }
812       }
813     }
814   }
815 }
816
817 wmGizmoGroup *WM_gizmomaptype_group_init_runtime_with_region(wmGizmoMapType *gzmap_type,
818                                                              wmGizmoGroupType *gzgt,
819                                                              ARegion *ar)
820 {
821   wmGizmoMap *gzmap = ar->gizmo_map;
822   BLI_assert(gzmap && gzmap->type == gzmap_type);
823   UNUSED_VARS_NDEBUG(gzmap_type);
824
825   wmGizmoGroup *gzgroup = wm_gizmogroup_new_from_type(gzmap, gzgt);
826
827   wm_gizmomap_highlight_set(gzmap, NULL, NULL, 0);
828
829   ED_region_tag_redraw(ar);
830
831   return gzgroup;
832 }
833
834 /**
835  * Unlike #WM_gizmomaptype_group_unlink this doesn't maintain correct state, simply free.
836  */
837 void WM_gizmomaptype_group_free(wmGizmoGroupTypeRef *gzgt_ref)
838 {
839   MEM_freeN(gzgt_ref);
840 }
841
842 void WM_gizmomaptype_group_unlink(bContext *C,
843                                   Main *bmain,
844                                   wmGizmoMapType *gzmap_type,
845                                   const wmGizmoGroupType *gzgt)
846 {
847   /* Free instances. */
848   for (bScreen *sc = bmain->screens.first; sc; sc = sc->id.next) {
849     for (ScrArea *sa = sc->areabase.first; sa; sa = sa->next) {
850       for (SpaceLink *sl = sa->spacedata.first; sl; sl = sl->next) {
851         ListBase *lb = (sl == sa->spacedata.first) ? &sa->regionbase : &sl->regionbase;
852         for (ARegion *ar = lb->first; ar; ar = ar->next) {
853           wmGizmoMap *gzmap = ar->gizmo_map;
854           if (gzmap && gzmap->type == gzmap_type) {
855             wmGizmoGroup *gzgroup, *gzgroup_next;
856             for (gzgroup = gzmap->groups.first; gzgroup; gzgroup = gzgroup_next) {
857               gzgroup_next = gzgroup->next;
858               if (gzgroup->type == gzgt) {
859                 BLI_assert(gzgroup->parent_gzmap == gzmap);
860                 wm_gizmogroup_free(C, gzgroup);
861                 ED_region_tag_redraw(ar);
862               }
863             }
864           }
865         }
866       }
867     }
868   }
869
870   /* Free types. */
871   wmGizmoGroupTypeRef *gzgt_ref = WM_gizmomaptype_group_find_ptr(gzmap_type, gzgt);
872   if (gzgt_ref) {
873     BLI_remlink(&gzmap_type->grouptype_refs, gzgt_ref);
874     WM_gizmomaptype_group_free(gzgt_ref);
875   }
876
877   /* Note, we may want to keep this keymap for editing */
878   WM_keymap_remove(gzgt->keyconf, gzgt->keymap);
879
880   BLI_assert(WM_gizmomaptype_group_find_ptr(gzmap_type, gzgt) == NULL);
881 }
882
883 void wm_gizmogrouptype_setup_keymap(wmGizmoGroupType *gzgt, wmKeyConfig *keyconf)
884 {
885   /* Use flag since setup_keymap may return NULL,
886    * in that case we better not keep calling it. */
887   if (gzgt->type_update_flag & WM_GIZMOMAPTYPE_KEYMAP_INIT) {
888     gzgt->keymap = gzgt->setup_keymap(gzgt, keyconf);
889     gzgt->keyconf = keyconf;
890     gzgt->type_update_flag &= ~WM_GIZMOMAPTYPE_KEYMAP_INIT;
891   }
892 }
893
894 /** \} */ /* wmGizmoGroupType */
895
896 /* -------------------------------------------------------------------- */
897 /** \name High Level Add/Remove API
898  *
899  * For use directly from operators & RNA registration.
900  *
901  * \note In context of gizmo API these names are a bit misleading,
902  * but for general use terms its OK.
903  * `WM_gizmo_group_type_add` would be more correctly called:
904  * `WM_gizmomaptype_grouptype_reference_link`
905  * but for general purpose API this is too detailed & annoying.
906  *
907  * \note We may want to return a value if there is nothing to remove.
908  *
909  * \{ */
910
911 void WM_gizmo_group_type_add_ptr_ex(wmGizmoGroupType *gzgt, wmGizmoMapType *gzmap_type)
912 {
913   WM_gizmomaptype_group_link_ptr(gzmap_type, gzgt);
914
915   WM_gizmoconfig_update_tag_init(gzmap_type, gzgt);
916 }
917 void WM_gizmo_group_type_add_ptr(wmGizmoGroupType *gzgt)
918 {
919   wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
920   WM_gizmo_group_type_add_ptr_ex(gzgt, gzmap_type);
921 }
922 void WM_gizmo_group_type_add(const char *idname)
923 {
924   wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
925   BLI_assert(gzgt != NULL);
926   WM_gizmo_group_type_add_ptr(gzgt);
927 }
928
929 bool WM_gizmo_group_type_ensure_ptr_ex(wmGizmoGroupType *gzgt, wmGizmoMapType *gzmap_type)
930 {
931   wmGizmoGroupTypeRef *gzgt_ref = WM_gizmomaptype_group_find_ptr(gzmap_type, gzgt);
932   if (gzgt_ref == NULL) {
933     WM_gizmo_group_type_add_ptr_ex(gzgt, gzmap_type);
934     return true;
935   }
936   return false;
937 }
938 bool WM_gizmo_group_type_ensure_ptr(wmGizmoGroupType *gzgt)
939 {
940   wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
941   return WM_gizmo_group_type_ensure_ptr_ex(gzgt, gzmap_type);
942 }
943 bool WM_gizmo_group_type_ensure(const char *idname)
944 {
945   wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
946   BLI_assert(gzgt != NULL);
947   return WM_gizmo_group_type_ensure_ptr(gzgt);
948 }
949
950 void WM_gizmo_group_type_remove_ptr_ex(struct Main *bmain,
951                                        wmGizmoGroupType *gzgt,
952                                        wmGizmoMapType *gzmap_type)
953 {
954   WM_gizmomaptype_group_unlink(NULL, bmain, gzmap_type, gzgt);
955   WM_gizmogrouptype_free_ptr(gzgt);
956 }
957 void WM_gizmo_group_type_remove_ptr(struct Main *bmain, wmGizmoGroupType *gzgt)
958 {
959   wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
960   WM_gizmo_group_type_remove_ptr_ex(bmain, gzgt, gzmap_type);
961 }
962 void WM_gizmo_group_type_remove(struct Main *bmain, const char *idname)
963 {
964   wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
965   BLI_assert(gzgt != NULL);
966   WM_gizmo_group_type_remove_ptr(bmain, gzgt);
967 }
968
969 void WM_gizmo_group_type_reinit_ptr_ex(struct Main *bmain,
970                                        wmGizmoGroupType *gzgt,
971                                        wmGizmoMapType *gzmap_type)
972 {
973   wmGizmoGroupTypeRef *gzgt_ref = WM_gizmomaptype_group_find_ptr(gzmap_type, gzgt);
974   BLI_assert(gzgt_ref != NULL);
975   UNUSED_VARS_NDEBUG(gzgt_ref);
976   WM_gizmomaptype_group_unlink(NULL, bmain, gzmap_type, gzgt);
977   WM_gizmo_group_type_add_ptr_ex(gzgt, gzmap_type);
978 }
979 void WM_gizmo_group_type_reinit_ptr(struct Main *bmain, wmGizmoGroupType *gzgt)
980 {
981   wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
982   WM_gizmo_group_type_reinit_ptr_ex(bmain, gzgt, gzmap_type);
983 }
984 void WM_gizmo_group_type_reinit(struct Main *bmain, const char *idname)
985 {
986   wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
987   BLI_assert(gzgt != NULL);
988   WM_gizmo_group_type_reinit_ptr(bmain, gzgt);
989 }
990
991 /* delayed versions */
992
993 void WM_gizmo_group_type_unlink_delayed_ptr_ex(wmGizmoGroupType *gzgt, wmGizmoMapType *gzmap_type)
994 {
995   WM_gizmoconfig_update_tag_remove(gzmap_type, gzgt);
996 }
997
998 void WM_gizmo_group_type_unlink_delayed_ptr(wmGizmoGroupType *gzgt)
999 {
1000   wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
1001   WM_gizmo_group_type_unlink_delayed_ptr_ex(gzgt, gzmap_type);
1002 }
1003
1004 void WM_gizmo_group_type_unlink_delayed(const char *idname)
1005 {
1006   wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
1007   BLI_assert(gzgt != NULL);
1008   WM_gizmo_group_type_unlink_delayed_ptr(gzgt);
1009 }
1010
1011 /** \} */