code cleanup: use const events for modal and invoke operators.
[blender.git] / source / blender / editors / armature / pose_group.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2008 Blender Foundation
19  * All rights reserved.
20  *
21  * Contributor(s): Joshua Leung
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  *
25  * Implementation of Bone Groups operators and editing API's
26  */
27
28 /** \file blender/editors/armature/pose_group.c
29  *  \ingroup edarmature
30  */
31
32 #include <string.h>
33
34 #include "MEM_guardedalloc.h"
35
36 #include "BLI_blenlib.h"
37
38 #include "DNA_armature_types.h"
39 #include "DNA_object_types.h"
40
41 #include "BKE_action.h"
42 #include "BKE_context.h"
43
44 #include "RNA_access.h"
45 #include "RNA_define.h"
46
47 #include "WM_api.h"
48 #include "WM_types.h"
49
50 #include "ED_armature.h"
51 #include "ED_screen.h"
52
53 #include "UI_interface.h"
54 #include "UI_resources.h"
55
56 #include "armature_intern.h"
57
58 /* ********************************************** */
59 /* Bone Groups */
60
61 static int pose_group_add_exec(bContext *C, wmOperator *UNUSED(op))
62 {
63         Object *ob = ED_pose_object_from_context(C);
64
65         /* only continue if there's an object */
66         if (ob == NULL)
67                 return OPERATOR_CANCELLED;
68         
69         /* for now, just call the API function for this */
70         BKE_pose_add_group(ob);
71         
72         /* notifiers for updates */
73         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
74         
75         return OPERATOR_FINISHED;
76 }
77
78 void POSE_OT_group_add(wmOperatorType *ot)
79 {
80         /* identifiers */
81         ot->name = "Add Bone Group";
82         ot->idname = "POSE_OT_group_add";
83         ot->description = "Add a new bone group";
84         
85         /* api callbacks */
86         ot->exec = pose_group_add_exec;
87         ot->poll = ED_operator_posemode_context;
88         
89         /* flags */
90         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
91 }
92
93
94 static int pose_group_remove_exec(bContext *C, wmOperator *UNUSED(op))
95 {
96         Object *ob = ED_pose_object_from_context(C);
97         
98         /* only continue if there's an object */
99         if (ob == NULL)
100                 return OPERATOR_CANCELLED;
101         
102         /* for now, just call the API function for this */
103         BKE_pose_remove_group(ob);
104         
105         /* notifiers for updates */
106         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
107         
108         return OPERATOR_FINISHED;
109 }
110
111 void POSE_OT_group_remove(wmOperatorType *ot)
112 {
113         /* identifiers */
114         ot->name = "Remove Bone Group";
115         ot->idname = "POSE_OT_group_remove";
116         ot->description = "Remove the active bone group";
117         
118         /* api callbacks */
119         ot->exec = pose_group_remove_exec;
120         ot->poll = ED_operator_posemode_context;
121         
122         /* flags */
123         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
124 }
125
126 /* ------------ */
127
128 /* invoke callback which presents a list of bone-groups for the user to choose from */
129 static int pose_groups_menu_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
130 {
131         Object *ob = ED_pose_object_from_context(C);
132         bPose *pose;
133         
134         uiPopupMenu *pup;
135         uiLayout *layout;
136         bActionGroup *grp;
137         int i;
138         
139         /* only continue if there's an object, and a pose there too */
140         if (ELEM(NULL, ob, ob->pose)) 
141                 return OPERATOR_CANCELLED;
142         pose = ob->pose;
143         
144         /* if there's no active group (or active is invalid), create a new menu to find it */
145         if (pose->active_group <= 0) {
146                 /* create a new menu, and start populating it with group names */
147                 pup = uiPupMenuBegin(C, op->type->name, ICON_NONE);
148                 layout = uiPupMenuLayout(pup);
149                 
150                 /* special entry - allow to create new group, then use that 
151                  *      (not to be used for removing though)
152                  */
153                 if (strstr(op->idname, "assign")) {
154                         uiItemIntO(layout, "New Group", ICON_NONE, op->idname, "type", 0);
155                         uiItemS(layout);
156                 }
157                 
158                 /* add entries for each group */
159                 for (grp = pose->agroups.first, i = 1; grp; grp = grp->next, i++)
160                         uiItemIntO(layout, grp->name, ICON_NONE, op->idname, "type", i);
161                         
162                 /* finish building the menu, and process it (should result in calling self again) */
163                 uiPupMenuEnd(C, pup);
164                 
165                 return OPERATOR_CANCELLED;
166         }
167         else {
168                 /* just use the active group index, and call the exec callback for the calling operator */
169                 RNA_int_set(op->ptr, "type", pose->active_group);
170                 return op->type->exec(C, op);
171         }
172 }
173
174 /* Assign selected pchans to the bone group that the user selects */
175 static int pose_group_assign_exec(bContext *C, wmOperator *op)
176 {
177         Object *ob = ED_pose_object_from_context(C);
178         bPose *pose;
179         short done = FALSE;
180
181         /* only continue if there's an object, and a pose there too */
182         if (ELEM(NULL, ob, ob->pose))
183                 return OPERATOR_CANCELLED;
184
185         pose = ob->pose;
186         
187         /* set the active group number to the one from operator props 
188          *  - if 0 after this, make a new group...
189          */
190         pose->active_group = RNA_int_get(op->ptr, "type");
191         if (pose->active_group == 0)
192                 BKE_pose_add_group(ob);
193         
194         /* add selected bones to group then */
195         CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones)
196         {
197                 pchan->agrp_index = pose->active_group;
198                 done = TRUE;
199         }
200         CTX_DATA_END;
201
202         /* notifiers for updates */
203         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
204         
205         /* report done status */
206         if (done)
207                 return OPERATOR_FINISHED;
208         else
209                 return OPERATOR_CANCELLED;
210 }
211
212 void POSE_OT_group_assign(wmOperatorType *ot)
213 {
214         /* identifiers */
215         ot->name = "Add Selected to Bone Group";
216         ot->idname = "POSE_OT_group_assign";
217         ot->description = "Add selected bones to the chosen bone group";
218         
219         /* api callbacks */
220         ot->invoke = pose_groups_menu_invoke;
221         ot->exec = pose_group_assign_exec;
222         ot->poll = ED_operator_posemode_context;
223         
224         /* flags */
225         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
226         
227         /* properties */
228         RNA_def_int(ot->srna, "type", 0, 0, INT_MAX, "Bone Group Index", "", 0, 10);
229 }
230
231
232 static int pose_group_unassign_exec(bContext *C, wmOperator *UNUSED(op))
233 {
234         Object *ob = ED_pose_object_from_context(C);
235         short done = FALSE;
236         
237         /* only continue if there's an object, and a pose there too */
238         if (ELEM(NULL, ob, ob->pose))
239                 return OPERATOR_CANCELLED;
240         
241         /* find selected bones to remove from all bone groups */
242         CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones)
243         {
244                 if (pchan->agrp_index) {
245                         pchan->agrp_index = 0;
246                         done = TRUE;
247                 }
248         }
249         CTX_DATA_END;
250         
251         /* notifiers for updates */
252         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
253         
254         /* report done status */
255         if (done)
256                 return OPERATOR_FINISHED;
257         else
258                 return OPERATOR_CANCELLED;
259 }
260
261 void POSE_OT_group_unassign(wmOperatorType *ot)
262 {
263         /* identifiers */
264         ot->name = "Remove Selected from Bone Groups";
265         ot->idname = "POSE_OT_group_unassign";
266         ot->description = "Remove selected bones from all bone groups";
267         
268         /* api callbacks */
269         ot->exec = pose_group_unassign_exec;
270         ot->poll = ED_operator_posemode_context;
271         
272         /* flags */
273         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
274 }
275
276 static int group_move_exec(bContext *C, wmOperator *op)
277 {
278         Object *ob = ED_pose_object_from_context(C);
279         bPose *pose = (ob) ? ob->pose : NULL;
280         bPoseChannel *pchan;
281         bActionGroup *grp;
282         int dir = RNA_enum_get(op->ptr, "direction");
283         int grpIndexA, grpIndexB;
284
285         if (ELEM(NULL, ob, pose))
286                 return OPERATOR_CANCELLED;
287         if (pose->active_group <= 0)
288                 return OPERATOR_CANCELLED;
289
290         /* get group to move */
291         grp = BLI_findlink(&pose->agroups, pose->active_group - 1);
292         if (grp == NULL)
293                 return OPERATOR_CANCELLED;
294
295         /* move bone group */
296         grpIndexA = pose->active_group;
297         if (dir == 1) { /* up */
298                 void *prev = grp->prev;
299                 
300                 if (prev == NULL)
301                         return OPERATOR_FINISHED;
302                         
303                 BLI_remlink(&pose->agroups, grp);
304                 BLI_insertlinkbefore(&pose->agroups, prev, grp);
305                 
306                 grpIndexB = grpIndexA - 1;
307                 pose->active_group--;
308         }
309         else { /* down */
310                 void *next = grp->next;
311                 
312                 if (next == NULL)
313                         return OPERATOR_FINISHED;
314                         
315                 BLI_remlink(&pose->agroups, grp);
316                 BLI_insertlinkafter(&pose->agroups, next, grp);
317                 
318                 grpIndexB = grpIndexA + 1;
319                 pose->active_group++;
320         }
321
322         /* fix changed bone group indices in bones (swap grpIndexA with grpIndexB) */
323         for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
324                 if (pchan->agrp_index == grpIndexB)
325                         pchan->agrp_index = grpIndexA;
326                 else if (pchan->agrp_index == grpIndexA)
327                         pchan->agrp_index = grpIndexB;
328         }
329
330         /* notifiers for updates */
331         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
332
333         return OPERATOR_FINISHED;
334 }
335
336 void POSE_OT_group_move(wmOperatorType *ot)
337 {
338         static EnumPropertyItem group_slot_move[] = {
339                 {1, "UP", 0, "Up", ""},
340                 {-1, "DOWN", 0, "Down", ""},
341                 {0, NULL, 0, NULL, NULL}
342         };
343
344         /* identifiers */
345         ot->name = "Move Bone Group";
346         ot->idname = "POSE_OT_group_move";
347         ot->description = "Change position of active Bone Group in list of Bone Groups";
348
349         /* api callbacks */
350         ot->exec = group_move_exec;
351         ot->poll = ED_operator_posemode_context;
352
353         /* flags */
354         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
355
356         RNA_def_enum(ot->srna, "direction", group_slot_move, 0, "Direction", "Direction to move, UP or DOWN");
357 }
358
359 /* bone group sort element */
360 typedef struct tSortActionGroup {
361         bActionGroup *agrp;
362         int index;
363 } tSortActionGroup;
364
365 /* compare bone groups by name */
366 static int compare_agroup(const void *sgrp_a_ptr, const void *sgrp_b_ptr)
367 {
368         tSortActionGroup *sgrp_a = (tSortActionGroup *)sgrp_a_ptr;
369         tSortActionGroup *sgrp_b = (tSortActionGroup *)sgrp_b_ptr;
370
371         return strcmp(sgrp_a->agrp->name, sgrp_b->agrp->name);
372 }
373
374 static int group_sort_exec(bContext *C, wmOperator *UNUSED(op))
375 {
376         Object *ob = ED_pose_object_from_context(C);
377         bPose *pose = (ob) ? ob->pose : NULL;
378         bPoseChannel *pchan;
379         tSortActionGroup *agrp_array;
380         bActionGroup *agrp;
381         int agrp_count;
382         int i;
383
384         if (ELEM(NULL, ob, pose))
385                 return OPERATOR_CANCELLED;
386         if (pose->active_group <= 0)
387                 return OPERATOR_CANCELLED;
388
389         /* create temporary array with bone groups and indices */
390         agrp_count = BLI_countlist(&pose->agroups);
391         agrp_array = MEM_mallocN(sizeof(tSortActionGroup) * agrp_count, "sort bone groups");
392         for (agrp = pose->agroups.first, i = 0; agrp; agrp = agrp->next, i++) {
393                 BLI_assert(i < agrp_count);
394                 agrp_array[i].agrp = agrp;
395                 agrp_array[i].index = i + 1;
396         }
397
398         /* sort bone groups by name */
399         qsort(agrp_array, agrp_count, sizeof(tSortActionGroup), compare_agroup);
400
401         /* create sorted bone group list from sorted array */
402         pose->agroups.first = pose->agroups.last = NULL;
403         for (i = 0; i < agrp_count; i++) {
404                 BLI_addtail(&pose->agroups, agrp_array[i].agrp);
405         }
406
407         /* fix changed bone group indizes in bones */
408         for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
409                 for (i = 0; i < agrp_count; i++) {
410                         if (pchan->agrp_index == agrp_array[i].index) {
411                                 pchan->agrp_index = i + 1;
412                                 break;
413                         }
414                 }
415         }
416
417         /* free temp resources */
418         MEM_freeN(agrp_array);
419
420         /* notifiers for updates */
421         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
422
423         return OPERATOR_FINISHED;
424 }
425
426 void POSE_OT_group_sort(wmOperatorType *ot)
427 {
428         /* identifiers */
429         ot->name = "Sort Bone Groups";
430         ot->idname = "POSE_OT_group_sort";
431         ot->description = "Sort Bone Groups by their names in ascending order";
432
433         /* api callbacks */
434         ot->exec = group_sort_exec;
435         ot->poll = ED_operator_posemode_context;
436
437         /* flags */
438         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
439 }
440
441 static void pose_group_select(bContext *C, Object *ob, int select)
442 {
443         bPose *pose = ob->pose;
444         
445         CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
446         {
447                 if ((pchan->bone->flag & BONE_UNSELECTABLE) == 0) {
448                         if (select) {
449                                 if (pchan->agrp_index == pose->active_group) 
450                                         pchan->bone->flag |= BONE_SELECTED;
451                         }
452                         else {
453                                 if (pchan->agrp_index == pose->active_group) 
454                                         pchan->bone->flag &= ~BONE_SELECTED;
455                         }
456                 }
457         }
458         CTX_DATA_END;
459 }
460
461 static int pose_group_select_exec(bContext *C, wmOperator *UNUSED(op))
462 {
463         Object *ob = ED_pose_object_from_context(C);
464         
465         /* only continue if there's an object, and a pose there too */
466         if (ELEM(NULL, ob, ob->pose))
467                 return OPERATOR_CANCELLED;
468         
469         pose_group_select(C, ob, 1);
470         
471         /* notifiers for updates */
472         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
473         
474         return OPERATOR_FINISHED;
475 }
476
477 void POSE_OT_group_select(wmOperatorType *ot)
478 {
479         /* identifiers */
480         ot->name = "Select Bones of Bone Group";
481         ot->idname = "POSE_OT_group_select";
482         ot->description = "Select bones in active Bone Group";
483         
484         /* api callbacks */
485         ot->exec = pose_group_select_exec;
486         ot->poll = ED_operator_posemode_context;
487         
488         /* flags */
489         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
490 }
491
492 static int pose_group_deselect_exec(bContext *C, wmOperator *UNUSED(op))
493 {
494         Object *ob = ED_pose_object_from_context(C);
495         
496         /* only continue if there's an object, and a pose there too */
497         if (ELEM(NULL, ob, ob->pose))
498                 return OPERATOR_CANCELLED;
499         
500         pose_group_select(C, ob, 0);
501         
502         /* notifiers for updates */
503         WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
504         
505         return OPERATOR_FINISHED;
506 }
507
508 void POSE_OT_group_deselect(wmOperatorType *ot)
509 {
510         /* identifiers */
511         ot->name = "Deselect Bone Group";
512         ot->idname = "POSE_OT_group_deselect";
513         ot->description = "Deselect bones of active Bone Group";
514         
515         /* api callbacks */
516         ot->exec = pose_group_deselect_exec;
517         ot->poll = ED_operator_posemode_context;
518         
519         /* flags */
520         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
521 }
522
523 /* ********************************************** */