Workspace: Move engines to workspace and Properties Editor cleanup
[blender.git] / source / blender / editors / screen / workspace_edit.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/editors/screen/workspace_edit.c
22  *  \ingroup edscr
23  */
24
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "BLI_utildefines.h"
29 #include "BLI_fileops.h"
30 #include "BLI_listbase.h"
31 #include "BLI_path_util.h"
32 #include "BLI_string.h"
33
34 #include "BKE_appdir.h"
35 #include "BKE_blendfile.h"
36 #include "BKE_context.h"
37 #include "BKE_idcode.h"
38 #include "BKE_main.h"
39 #include "BKE_library.h"
40 #include "BKE_report.h"
41 #include "BKE_scene.h"
42 #include "BKE_screen.h"
43 #include "BKE_workspace.h"
44
45 #include "BLO_readfile.h"
46
47 #include "DNA_object_types.h"
48 #include "DNA_screen_types.h"
49 #include "DNA_windowmanager_types.h"
50 #include "DNA_workspace_types.h"
51
52 #include "ED_object.h"
53 #include "ED_screen.h"
54
55 #include "MEM_guardedalloc.h"
56
57 #include "RNA_access.h"
58
59 #include "UI_interface.h"
60 #include "UI_resources.h"
61
62 #include "WM_api.h"
63 #include "WM_types.h"
64
65 #include "screen_intern.h"
66
67
68 /** \name Workspace API
69  *
70  * \brief API for managing workspaces and their data.
71  * \{ */
72
73 WorkSpace *ED_workspace_add(
74         Main *bmain, const char *name, SceneLayer *act_render_layer, ViewRender *view_render)
75 {
76         WorkSpace *workspace = BKE_workspace_add(bmain, name);
77
78 #ifdef USE_WORKSPACE_MODE
79         BKE_workspace_object_mode_set(workspace, OB_MODE_OBJECT);
80 #endif
81
82         BKE_workspace_render_layer_set(workspace, act_render_layer);
83         BKE_viewrender_copy(&workspace->view_render, view_render);
84
85         return workspace;
86 }
87
88 #ifdef USE_WORKSPACE_MODE
89 /**
90  * Changes the object mode (if needed) to the one set in \a workspace_new.
91  * Object mode is still stored on object level. In future it should all be workspace level instead.
92  */
93 static void workspace_change_update_mode(
94         const WorkSpace *workspace_old, const WorkSpace *workspace_new,
95         bContext *C, Object *ob_act, ReportList *reports)
96 {
97         ObjectMode mode_old = BKE_workspace_object_mode_get(workspace_old);
98         ObjectMode mode_new = BKE_workspace_object_mode_get(workspace_new);
99
100         if (mode_old != mode_new) {
101                 ED_object_mode_compat_set(C, ob_act, mode_new, reports);
102                 ED_object_toggle_modes(C, mode_new);
103         }
104 }
105 #endif
106
107 static void workspace_change_update_render_layer(
108         WorkSpace *workspace_new, const WorkSpace *workspace_old)
109 {
110         if (!BKE_workspace_render_layer_get(workspace_new)) {
111                 BKE_workspace_render_layer_set(workspace_new, BKE_workspace_render_layer_get(workspace_old));
112         }
113 }
114
115 static void workspace_change_update(
116         WorkSpace *workspace_new, const WorkSpace *workspace_old,
117         bContext *C, wmWindowManager *wm)
118 {
119         /* needs to be done before changing mode! (to ensure right context) */
120         workspace_change_update_render_layer(workspace_new, workspace_old);
121 #ifdef USE_WORKSPACE_MODE
122         workspace_change_update_mode(workspace_old, workspace_new, C, CTX_data_active_object(C), &wm->reports);
123 #else
124         UNUSED_VARS(C, wm);
125 #endif
126 }
127
128 static bool workspace_change_find_new_layout_cb(const WorkSpaceLayout *layout, void *UNUSED(arg))
129 {
130         /* return false to stop the iterator if we've found a layout that can be activated */
131         return workspace_layout_set_poll(layout) ? false : true;
132 }
133
134 static WorkSpaceLayout *workspace_change_get_new_layout(
135         WorkSpace *workspace_new, wmWindow *win)
136 {
137         /* ED_workspace_duplicate may have stored a layout to activate once the workspace gets activated. */
138         WorkSpaceLayout *layout_new;
139         bScreen *screen_new;
140
141         if (win->workspace_hook->temp_workspace_store) {
142                 layout_new = win->workspace_hook->temp_layout_store;
143         }
144         else {
145                 layout_new = BKE_workspace_hook_layout_for_workspace_get(win->workspace_hook, workspace_new);
146                 if (!layout_new) {
147                         layout_new = BKE_workspace_layouts_get(workspace_new)->first;
148                 }
149         }
150         screen_new = BKE_workspace_layout_screen_get(layout_new);
151
152         if (screen_new->winid) {
153                 /* screen is already used, try to find a free one */
154                 WorkSpaceLayout *layout_temp = BKE_workspace_layout_iter_circular(
155                                                    workspace_new, layout_new, workspace_change_find_new_layout_cb,
156                                                    NULL, false);
157                 if (!layout_temp) {
158                         /* fallback solution: duplicate layout */
159                         layout_temp = ED_workspace_layout_duplicate(workspace_new, layout_new, win);
160                 }
161                 layout_new = layout_temp;
162         }
163
164         return layout_new;
165 }
166
167 /**
168  * \brief Change the active workspace.
169  *
170  * Operator call, WM + Window + screen already existed before
171  * Pretty similar to #ED_screen_change since changing workspace also changes screen.
172  *
173  * \warning Do NOT call in area/region queues!
174  * \returns if workspace changing was successful.
175  */
176 bool ED_workspace_change(
177         WorkSpace *workspace_new, bContext *C, wmWindowManager *wm, wmWindow *win)
178 {
179         Main *bmain = CTX_data_main(C);
180         WorkSpace *workspace_old = WM_window_get_active_workspace(win);
181         WorkSpaceLayout *layout_new = workspace_change_get_new_layout(workspace_new, win);
182         bScreen *screen_new = BKE_workspace_layout_screen_get(layout_new);
183         bScreen *screen_old = BKE_workspace_active_screen_get(win->workspace_hook);
184
185         win->workspace_hook->temp_layout_store = NULL;
186         if (workspace_old == workspace_new) {
187                 /* Could also return true, everything that needs to be done was done (nothing :P), but nothing changed */
188                 return false;
189         }
190
191         screen_new = screen_change_prepare(screen_old, screen_new, bmain, C, win);
192         BLI_assert(BKE_workspace_layout_screen_get(layout_new) == screen_new);
193
194         if (screen_new) {
195                 WM_window_set_active_layout(win, workspace_new, layout_new);
196                 WM_window_set_active_workspace(win, workspace_new);
197
198                 /* update screen *after* changing workspace - which also causes the actual screen change */
199                 screen_changed_update(C, win, screen_new);
200                 workspace_change_update(workspace_new, workspace_old, C, wm);
201
202                 BLI_assert(BKE_workspace_render_layer_get(workspace_new) != NULL);
203                 BLI_assert(CTX_wm_workspace(C) == workspace_new);
204
205                 return true;
206         }
207
208         return false;
209 }
210
211 /**
212  * Duplicate a workspace including its layouts. Does not activate the workspace, but
213  * it stores the screen-layout to be activated (BKE_workspace_temp_layout_store)
214  */
215 WorkSpace *ED_workspace_duplicate(
216         WorkSpace *workspace_old, Main *bmain, wmWindow *win)
217 {
218         WorkSpaceLayout *layout_active_old = BKE_workspace_active_layout_get(win->workspace_hook);
219         ListBase *layouts_old = BKE_workspace_layouts_get(workspace_old);
220         WorkSpace *workspace_new = ED_workspace_add(
221                 bmain, workspace_old->id.name + 2,
222                 BKE_workspace_render_layer_get(workspace_old),
223                 &workspace_old->view_render);
224         ListBase *transform_orientations_old = BKE_workspace_transform_orientations_get(workspace_old);
225         ListBase *transform_orientations_new = BKE_workspace_transform_orientations_get(workspace_new);
226
227 #ifdef USE_WORKSPACE_MODE
228         BKE_workspace_object_mode_set(workspace_new, BKE_workspace_object_mode_get(workspace_old));
229 #endif
230         BLI_duplicatelist(transform_orientations_new, transform_orientations_old);
231
232         for (WorkSpaceLayout *layout_old = layouts_old->first; layout_old; layout_old = layout_old->next) {
233                 WorkSpaceLayout *layout_new = ED_workspace_layout_duplicate(workspace_new, layout_old, win);
234
235                 if (layout_active_old == layout_old) {
236                         win->workspace_hook->temp_layout_store = layout_new;
237                 }
238         }
239         return workspace_new;
240 }
241
242 /**
243  * \return if succeeded.
244  */
245 bool ED_workspace_delete(
246         WorkSpace *workspace, Main *bmain, bContext *C, wmWindowManager *wm)
247 {
248         ID *workspace_id = (ID *)workspace;
249
250         if (BLI_listbase_is_single(&bmain->workspaces)) {
251                 return false;
252         }
253
254         for (wmWindow *win = wm->windows.first; win; win = win->next) {
255                 WorkSpace *prev = workspace_id->prev;
256                 WorkSpace *next = workspace_id->next;
257
258                 ED_workspace_change((prev != NULL) ? prev : next, C, wm, win);
259         }
260         BKE_libblock_free(bmain, workspace_id);
261
262         return true;
263 }
264
265 /**
266  * Some editor data may need to be synced with scene data (3D View camera and layers).
267  * This function ensures data is synced for editors in active layout of \a workspace.
268  */
269 void ED_workspace_scene_data_sync(
270         WorkSpaceInstanceHook *hook, Scene *scene)
271 {
272         bScreen *screen = BKE_workspace_active_screen_get(hook);
273         BKE_screen_view3d_scene_sync(screen, scene);
274 }
275
276 void ED_workspace_render_layer_unset(
277         const Main *bmain, const SceneLayer *layer_unset, SceneLayer *layer_new)
278 {
279         for (WorkSpace *workspace = bmain->workspaces.first; workspace; workspace = workspace->id.next) {
280                 if (BKE_workspace_render_layer_get(workspace) == layer_unset) {
281                         BKE_workspace_render_layer_set(workspace, layer_new);
282                 }
283         }
284 }
285
286 /** \} Workspace API */
287
288
289 /** \name Workspace Operators
290  *
291  * \{ */
292
293 static int workspace_new_exec(bContext *C, wmOperator *UNUSED(op))
294 {
295         Main *bmain = CTX_data_main(C);
296         wmWindow *win = CTX_wm_window(C);
297         WorkSpace *workspace = ED_workspace_duplicate(WM_window_get_active_workspace(win), bmain, win);
298
299         WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_SET, workspace);
300
301         return OPERATOR_FINISHED;
302 }
303
304 static void WORKSPACE_OT_workspace_duplicate(wmOperatorType *ot)
305 {
306         /* identifiers */
307         ot->name = "New Workspace";
308         ot->description = "Add a new workspace";
309         ot->idname = "WORKSPACE_OT_workspace_duplicate";
310
311         /* api callbacks */
312         ot->exec = workspace_new_exec;
313         ot->poll = WM_operator_winactive;
314 }
315
316 static int workspace_delete_exec(bContext *C, wmOperator *UNUSED(op))
317 {
318         Main *bmain = CTX_data_main(C);
319         wmWindowManager *wm = CTX_wm_manager(C);
320         wmWindow *win = CTX_wm_window(C);
321
322         ED_workspace_delete(WM_window_get_active_workspace(win), bmain, C, wm);
323
324         return OPERATOR_FINISHED;
325 }
326
327 static void WORKSPACE_OT_workspace_delete(wmOperatorType *ot)
328 {
329         /* identifiers */
330         ot->name = "Delete Workspace";
331         ot->description = "Delete the active workspace";
332         ot->idname = "WORKSPACE_OT_workspace_delete";
333
334         /* api callbacks */
335         ot->exec = workspace_delete_exec;
336 }
337
338 static void workspace_config_file_path_from_folder_id(
339         const Main *bmain, int folder_id, char *r_path)
340 {
341         const char *app_template = U.app_template[0] ? U.app_template : NULL;
342         const char * const cfgdir = BKE_appdir_folder_id(folder_id, app_template);
343
344         if (cfgdir) {
345                 BLI_make_file_string(bmain->name, r_path, cfgdir, BLENDER_WORKSPACES_FILE);
346         }
347         else {
348                 r_path[0] = '\0';
349         }
350 }
351
352 ATTR_NONNULL(1)
353 static WorkspaceConfigFileData *workspace_config_file_read(
354         const Main *bmain, ReportList *reports)
355 {
356         char workspace_config_path[FILE_MAX];
357         bool has_path = false;
358
359         workspace_config_file_path_from_folder_id(bmain, BLENDER_USER_CONFIG, workspace_config_path);
360         if (BLI_exists(workspace_config_path)) {
361                 has_path = true;
362         }
363         else {
364                 workspace_config_file_path_from_folder_id(bmain, BLENDER_DATAFILES, workspace_config_path);
365                 if (BLI_exists(workspace_config_path)) {
366                         has_path = true;
367                 }
368         }
369
370         return has_path ? BKE_blendfile_workspace_config_read(workspace_config_path, reports) : NULL;
371 }
372
373 static void workspace_append_button(
374         uiLayout *layout, wmOperatorType *ot_append, const WorkSpace *workspace, const Main *from_main)
375 {
376         const ID *id = (ID *)workspace;
377         PointerRNA opptr;
378         char lib_path[FILE_MAX_LIBEXTRA];
379
380         BLI_path_join(
381                 lib_path, sizeof(lib_path), from_main->name, BKE_idcode_to_name(GS(id->name)), NULL);
382
383         BLI_assert(STREQ(ot_append->idname, "WM_OT_append"));
384         opptr = uiItemFullO_ptr(
385                     layout, ot_append, workspace->id.name + 2, ICON_NONE, NULL,
386                     WM_OP_EXEC_DEFAULT, UI_ITEM_O_RETURN_PROPS);
387         RNA_string_set(&opptr, "directory", lib_path);
388         RNA_string_set(&opptr, "filename", id->name + 2);
389 }
390
391 ATTR_NONNULL(1, 2)
392 static void workspace_config_file_append_buttons(
393         uiLayout *layout, const Main *bmain, ReportList *reports)
394 {
395         WorkspaceConfigFileData *workspace_config = workspace_config_file_read(bmain, reports);
396
397         if (workspace_config) {
398                 wmOperatorType *ot_append = WM_operatortype_find("WM_OT_append", true);
399
400                 for (WorkSpace *workspace = workspace_config->workspaces.first; workspace; workspace = workspace->id.next) {
401                         workspace_append_button(layout, ot_append, workspace, workspace_config->main);
402                 }
403
404                 BKE_blendfile_workspace_config_data_free(workspace_config);
405         }
406 }
407
408 static int workspace_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
409 {
410         const Main *bmain = CTX_data_main(C);
411
412         uiPopupMenu *pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
413         uiLayout *layout = UI_popup_menu_layout(pup);
414
415         uiItemO(layout, "Duplicate Current", ICON_NONE, "WORKSPACE_OT_workspace_duplicate");
416         uiItemS(layout);
417         workspace_config_file_append_buttons(layout, bmain, op->reports);
418
419         UI_popup_menu_end(C, pup);
420
421         return OPERATOR_INTERFACE;
422 }
423
424 static void WORKSPACE_OT_workspace_add_menu(wmOperatorType *ot)
425 {
426         /* identifiers */
427         ot->name = "Add Workspace";
428         ot->description = "Add a new workspace by duplicating the current one or appending one "
429                           "from the user configuration";
430         ot->idname = "WORKSPACE_OT_workspace_add_menu";
431
432         /* api callbacks */
433         ot->invoke = workspace_add_invoke;
434 }
435
436 void ED_operatortypes_workspace(void)
437 {
438         WM_operatortype_append(WORKSPACE_OT_workspace_duplicate);
439         WM_operatortype_append(WORKSPACE_OT_workspace_delete);
440         WM_operatortype_append(WORKSPACE_OT_workspace_add_menu);
441 }
442
443 /** \} Workspace Operators */