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