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