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