Tool System: per space/mode tool support
[blender.git] / source / blender / blenkernel / intern / workspace.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  * ***** END GPL LICENSE BLOCK *****
19  */
20
21 /** \file blender/blenkernel/intern/workspace.c
22  *  \ingroup bke
23  */
24
25 /* allow accessing private members of DNA_workspace_types.h */
26 #define DNA_PRIVATE_WORKSPACE_ALLOW
27
28 #include <stdlib.h>
29
30 #include "BLI_utildefines.h"
31 #include "BLI_string.h"
32 #include "BLI_string_utf8.h"
33 #include "BLI_string_utils.h"
34 #include "BLI_listbase.h"
35
36 #include "BKE_global.h"
37 #include "BKE_library.h"
38 #include "BKE_main.h"
39 #include "BKE_scene.h"
40 #include "BKE_screen.h"
41 #include "BKE_object.h"
42 #include "BKE_workspace.h"
43
44 #include "DNA_object_types.h"
45 #include "DNA_scene_types.h"
46 #include "DNA_screen_types.h"
47 #include "DNA_workspace_types.h"
48
49 #include "DEG_depsgraph.h"
50
51 #include "MEM_guardedalloc.h"
52
53
54 /* -------------------------------------------------------------------- */
55 /* Internal utils */
56
57 static void workspace_layout_name_set(
58         WorkSpace *workspace, WorkSpaceLayout *layout, const char *new_name)
59 {
60         BLI_strncpy(layout->name, new_name, sizeof(layout->name));
61         BLI_uniquename(&workspace->layouts, layout, "Layout", '.', offsetof(WorkSpaceLayout, name), sizeof(layout->name));
62 }
63
64 /**
65  * This should only be used directly when it is to be expected that there isn't
66  * a layout within \a workspace that wraps \a screen. Usually - especially outside
67  * of BKE_workspace - #BKE_workspace_layout_find should be used!
68  */
69 static WorkSpaceLayout *workspace_layout_find_exec(
70         const WorkSpace *workspace, const bScreen *screen)
71 {
72         return BLI_findptr(&workspace->layouts, screen, offsetof(WorkSpaceLayout, screen));
73 }
74
75 static void workspace_relation_add(
76         ListBase *relation_list, void *parent, void *data)
77 {
78         WorkSpaceDataRelation *relation = MEM_callocN(sizeof(*relation), __func__);
79         relation->parent = parent;
80         relation->value = data;
81         /* add to head, if we switch back to it soon we find it faster. */
82         BLI_addhead(relation_list, relation);
83 }
84 static void workspace_relation_remove(
85         ListBase *relation_list, WorkSpaceDataRelation *relation)
86 {
87         BLI_remlink(relation_list, relation);
88         MEM_freeN(relation);
89 }
90
91 static void workspace_relation_ensure_updated(
92         ListBase *relation_list, void *parent, void *data)
93 {
94         WorkSpaceDataRelation *relation = BLI_findptr(relation_list, parent, offsetof(WorkSpaceDataRelation, parent));
95         if (relation != NULL) {
96                 relation->value = data;
97                 /* reinsert at the head of the list, so that more commonly used relations are found faster. */
98                 BLI_remlink(relation_list, relation);
99                 BLI_addhead(relation_list, relation);
100         }
101         else {
102                 /* no matching relation found, add new one */
103                 workspace_relation_add(relation_list, parent, data);
104         }
105 }
106
107 static void *workspace_relation_get_data_matching_parent(
108         const ListBase *relation_list, const void *parent)
109 {
110         WorkSpaceDataRelation *relation = BLI_findptr(relation_list, parent, offsetof(WorkSpaceDataRelation, parent));
111         if (relation != NULL) {
112                 return relation->value;
113         }
114         else {
115                 return NULL;
116         }
117 }
118
119 static void workspace_relation_remove_from_value(
120         ListBase *relation_list, const void *value)
121 {
122         for (WorkSpaceDataRelation *relation = relation_list->first, *relation_next; relation; relation = relation_next) {
123                 relation_next = relation->next;
124
125                 if (relation->value == value) {
126                         workspace_relation_remove(relation_list, relation);
127                 }
128         }
129 }
130
131 /**
132  * Checks if \a screen is already used within any workspace. A screen should never be assigned to multiple
133  * WorkSpaceLayouts, but that should be ensured outside of the BKE_workspace module and without such checks.
134  * Hence, this should only be used as assert check before assigining a screen to a workspace.
135  */
136 #ifndef NDEBUG
137 static bool workspaces_is_screen_used
138 #else
139 static bool UNUSED_FUNCTION(workspaces_is_screen_used)
140 #endif
141         (const Main *bmain, bScreen *screen)
142 {
143         for (WorkSpace *workspace = bmain->workspaces.first; workspace; workspace = workspace->id.next) {
144                 if (workspace_layout_find_exec(workspace, screen)) {
145                         return true;
146                 }
147         }
148
149         return false;
150 }
151
152 /* -------------------------------------------------------------------- */
153 /* Create, delete, init */
154
155 WorkSpace *BKE_workspace_add(Main *bmain, const char *name)
156 {
157         WorkSpace *new_workspace = BKE_libblock_alloc(bmain, ID_WS, name, 0);
158         return new_workspace;
159 }
160
161 /**
162  * The function that actually frees the workspace data (not workspace itself). It shouldn't be called
163  * directly, instead #BKE_workspace_remove should be, which calls this through #BKE_libblock_free then.
164  *
165  * Should something like a bke_internal.h be added, this should go there!
166  */
167 void BKE_workspace_free(WorkSpace *workspace)
168 {
169         BKE_workspace_relations_free(&workspace->hook_layout_relations);
170         BKE_workspace_relations_free(&workspace->scene_viewlayer_relations);
171
172         BLI_freelistN(&workspace->owner_ids);
173         BLI_freelistN(&workspace->layouts);
174
175         for (bToolRef *tref = workspace->tools.first, *tref_next; tref; tref = tref_next) {
176                 tref_next = tref->next;
177                 if (tref->runtime) {
178                         MEM_freeN(tref->runtime);
179                 }
180         }
181         BLI_freelistN(&workspace->tools);
182 }
183
184 /**
185  * Remove \a workspace by freeing itself and its data. This is a higher-level wrapper that
186  * calls #BKE_workspace_free (through #BKE_libblock_free) to free the workspace data, and frees
187  * other data-blocks owned by \a workspace and its layouts (currently that is screens only).
188  *
189  * Always use this to remove (and free) workspaces. Don't free non-ID workspace members here.
190  */
191 void BKE_workspace_remove(Main *bmain, WorkSpace *workspace)
192 {
193         for (WorkSpaceLayout *layout = workspace->layouts.first, *layout_next; layout; layout = layout_next) {
194                 layout_next = layout->next;
195                 BKE_workspace_layout_remove(bmain, workspace, layout);
196         }
197         BKE_libblock_free(bmain, workspace);
198 }
199
200 WorkSpaceInstanceHook *BKE_workspace_instance_hook_create(const Main *bmain)
201 {
202         WorkSpaceInstanceHook *hook = MEM_callocN(sizeof(WorkSpaceInstanceHook), __func__);
203
204         /* set an active screen-layout for each possible window/workspace combination */
205         for (WorkSpace *workspace = bmain->workspaces.first; workspace; workspace = workspace->id.next) {
206                 BKE_workspace_hook_layout_for_workspace_set(hook, workspace, workspace->layouts.first);
207         }
208
209         return hook;
210 }
211 void BKE_workspace_instance_hook_free(const Main *bmain, WorkSpaceInstanceHook *hook)
212 {
213         /* workspaces should never be freed before wm (during which we call this function) */
214         BLI_assert(!BLI_listbase_is_empty(&bmain->workspaces));
215
216         /* Free relations for this hook */
217         for (WorkSpace *workspace = bmain->workspaces.first; workspace; workspace = workspace->id.next) {
218                 for (WorkSpaceDataRelation *relation = workspace->hook_layout_relations.first, *relation_next;
219                      relation;
220                      relation = relation_next)
221                 {
222                         relation_next = relation->next;
223                         if (relation->parent == hook) {
224                                 workspace_relation_remove(&workspace->hook_layout_relations, relation);
225                         }
226                 }
227         }
228
229         MEM_freeN(hook);
230 }
231
232 /**
233  * Add a new layout to \a workspace for \a screen.
234  */
235 WorkSpaceLayout *BKE_workspace_layout_add(
236         WorkSpace *workspace,
237         bScreen *screen,
238         const char *name)
239 {
240         WorkSpaceLayout *layout = MEM_callocN(sizeof(*layout), __func__);
241
242         BLI_assert(!workspaces_is_screen_used(G.main, screen));
243         layout->screen = screen;
244         workspace_layout_name_set(workspace, layout, name);
245         BLI_addtail(&workspace->layouts, layout);
246
247         return layout;
248 }
249
250 void BKE_workspace_layout_remove(
251         Main *bmain,
252         WorkSpace *workspace, WorkSpaceLayout *layout)
253 {
254         BKE_libblock_free(bmain, BKE_workspace_layout_screen_get(layout));
255         BLI_freelinkN(&workspace->layouts, layout);
256 }
257
258 void BKE_workspace_relations_free(
259         ListBase *relation_list)
260 {
261         for (WorkSpaceDataRelation *relation = relation_list->first, *relation_next; relation; relation = relation_next) {
262                 relation_next = relation->next;
263                 workspace_relation_remove(relation_list, relation);
264         }
265 }
266
267
268 /* -------------------------------------------------------------------- */
269 /* General Utils */
270
271 void BKE_workspace_view_layer_remove_references(
272         const Main *bmain,
273         const ViewLayer *view_layer)
274 {
275         for (WorkSpace *workspace = bmain->workspaces.first; workspace; workspace = workspace->id.next) {
276                 workspace_relation_remove_from_value(&workspace->scene_viewlayer_relations, view_layer);
277         }
278 }
279
280 WorkSpaceLayout *BKE_workspace_layout_find(
281         const WorkSpace *workspace, const bScreen *screen)
282 {
283         WorkSpaceLayout *layout = workspace_layout_find_exec(workspace, screen);
284         if (layout) {
285                 return layout;
286         }
287
288         printf("%s: Couldn't find layout in this workspace: '%s' screen: '%s'. "
289                "This should not happen!\n",
290                __func__, workspace->id.name + 2, screen->id.name + 2);
291
292         return NULL;
293 }
294
295 /**
296  * Find the layout for \a screen without knowing which workspace to look in.
297  * Can also be used to find the workspace that contains \a screen.
298  *
299  * \param r_workspace: Optionally return the workspace that contains the looked up layout (if found).
300  */
301 WorkSpaceLayout *BKE_workspace_layout_find_global(
302         const Main *bmain, const bScreen *screen,
303         WorkSpace **r_workspace)
304 {
305         WorkSpaceLayout *layout;
306
307         if (r_workspace) {
308                 *r_workspace = NULL;
309         }
310
311         for (WorkSpace *workspace = bmain->workspaces.first; workspace; workspace = workspace->id.next) {
312                 if ((layout = workspace_layout_find_exec(workspace, screen))) {
313                         if (r_workspace) {
314                                 *r_workspace = workspace;
315                         }
316
317                         return layout;
318                 }
319         }
320
321         return NULL;
322 }
323
324 /**
325  * Circular workspace layout iterator.
326  *
327  * \param callback: Custom function which gets executed for each layout. Can return false to stop iterating.
328  * \param arg: Custom data passed to each \a callback call.
329  *
330  * \return the layout at which \a callback returned false.
331  */
332 WorkSpaceLayout *BKE_workspace_layout_iter_circular(
333         const WorkSpace *workspace, WorkSpaceLayout *start,
334         bool (*callback)(const WorkSpaceLayout *layout, void *arg),
335         void *arg, const bool iter_backward)
336 {
337         WorkSpaceLayout *iter_layout;
338
339         if (iter_backward) {
340                 LISTBASE_CIRCULAR_BACKWARD_BEGIN(&workspace->layouts, iter_layout, start)
341                 {
342                         if (!callback(iter_layout, arg)) {
343                                 return iter_layout;
344                         }
345                 }
346                 LISTBASE_CIRCULAR_BACKWARD_END(&workspace->layouts, iter_layout, start);
347         }
348         else {
349                 LISTBASE_CIRCULAR_FORWARD_BEGIN(&workspace->layouts, iter_layout, start)
350                 {
351                         if (!callback(iter_layout, arg)) {
352                                 return iter_layout;
353                         }
354                 }
355                 LISTBASE_CIRCULAR_FORWARD_END(&workspace->layouts, iter_layout, start)
356         }
357
358         return NULL;
359 }
360
361
362 /* -------------------------------------------------------------------- */
363 /* Getters/Setters */
364
365 WorkSpace *BKE_workspace_active_get(WorkSpaceInstanceHook *hook)
366 {
367         return hook->active;
368 }
369 void BKE_workspace_active_set(WorkSpaceInstanceHook *hook, WorkSpace *workspace)
370 {
371         hook->active = workspace;
372         if (workspace) {
373                 WorkSpaceLayout *layout = workspace_relation_get_data_matching_parent(&workspace->hook_layout_relations, hook);
374                 if (layout) {
375                         hook->act_layout = layout;
376                 }
377         }
378 }
379
380 WorkSpaceLayout *BKE_workspace_active_layout_get(const WorkSpaceInstanceHook *hook)
381 {
382         return hook->act_layout;
383 }
384 void BKE_workspace_active_layout_set(WorkSpaceInstanceHook *hook, WorkSpaceLayout *layout)
385 {
386         hook->act_layout = layout;
387 }
388
389 bScreen *BKE_workspace_active_screen_get(const WorkSpaceInstanceHook *hook)
390 {
391         return hook->act_layout->screen;
392 }
393 void BKE_workspace_active_screen_set(WorkSpaceInstanceHook *hook, WorkSpace *workspace, bScreen *screen)
394 {
395         /* we need to find the WorkspaceLayout that wraps this screen */
396         WorkSpaceLayout *layout = BKE_workspace_layout_find(hook->active, screen);
397         BKE_workspace_hook_layout_for_workspace_set(hook, workspace, layout);
398 }
399
400 Base *BKE_workspace_active_base_get(const WorkSpace *workspace, const Scene *scene)
401 {
402         ViewLayer *view_layer = BKE_workspace_view_layer_get(workspace, scene);
403         return view_layer->basact;
404 }
405
406 ViewLayer *BKE_workspace_view_layer_get(const WorkSpace *workspace, const Scene *scene)
407 {
408         return workspace_relation_get_data_matching_parent(&workspace->scene_viewlayer_relations, scene);
409 }
410 void BKE_workspace_view_layer_set(WorkSpace *workspace, ViewLayer *layer, Scene *scene)
411 {
412         workspace_relation_ensure_updated(&workspace->scene_viewlayer_relations, scene, layer);
413 }
414
415 ListBase *BKE_workspace_layouts_get(WorkSpace *workspace)
416 {
417         return &workspace->layouts;
418 }
419
420
421 const char *BKE_workspace_layout_name_get(const WorkSpaceLayout *layout)
422 {
423         return layout->name;
424 }
425 void BKE_workspace_layout_name_set(WorkSpace *workspace, WorkSpaceLayout *layout, const char *new_name)
426 {
427         workspace_layout_name_set(workspace, layout, new_name);
428 }
429
430 bScreen *BKE_workspace_layout_screen_get(const WorkSpaceLayout *layout)
431 {
432         return layout->screen;
433 }
434 void BKE_workspace_layout_screen_set(WorkSpaceLayout *layout, bScreen *screen)
435 {
436         layout->screen = screen;
437 }
438
439 WorkSpaceLayout *BKE_workspace_hook_layout_for_workspace_get(
440         const WorkSpaceInstanceHook *hook, const WorkSpace *workspace)
441 {
442         return workspace_relation_get_data_matching_parent(&workspace->hook_layout_relations, hook);
443 }
444 void BKE_workspace_hook_layout_for_workspace_set(
445         WorkSpaceInstanceHook *hook, WorkSpace *workspace, WorkSpaceLayout *layout)
446 {
447         hook->act_layout = layout;
448         workspace_relation_ensure_updated(&workspace->hook_layout_relations, hook, layout);
449 }
450
451 /* Update / evaluate */
452
453 void BKE_workspace_update_tagged(Main *bmain,
454                                  WorkSpace *workspace,
455                                  Scene *scene)
456 {
457         ViewLayer *view_layer = BKE_workspace_view_layer_get(workspace, scene);
458         struct Depsgraph *depsgraph = BKE_scene_get_depsgraph(scene,
459                                                               view_layer,
460                                                               true);
461         BKE_scene_graph_update_tagged(depsgraph, bmain);
462 }
463
464
465 bool BKE_workspace_owner_id_check(
466         const WorkSpace *workspace, const char *owner_id)
467 {
468         if ((*owner_id == '\0') ||
469             ((workspace->flags & WORKSPACE_USE_FILTER_BY_ORIGIN) == 0))
470         {
471                 return true;
472         }
473         else {
474                 /* we could use hash lookup, for now this list is highly under < ~16 items. */
475                 return BLI_findstring(&workspace->owner_ids, owner_id, offsetof(wmOwnerID, name)) != NULL;
476         }
477 }
478