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 edscr
19 */
20
21 #include <stdlib.h>
22 #include <string.h>
23
24 #include "BLI_fileops.h"
25 #include "BLI_listbase.h"
26 #include "BLI_path_util.h"
27 #include "BLI_utildefines.h"
28
29 #include "BKE_appdir.h"
30 #include "BKE_blendfile.h"
31 #include "BKE_context.h"
32 #include "BKE_lib_id.h"
33 #include "BKE_main.h"
34 #include "BKE_screen.h"
35 #include "BKE_workspace.h"
36
37 #include "BLO_readfile.h"
38
39 #include "DNA_screen_types.h"
40 #include "DNA_windowmanager_types.h"
41 #include "DNA_workspace_types.h"
42
43 #include "ED_datafiles.h"
44 #include "ED_object.h"
45 #include "ED_screen.h"
46
47 #include "RNA_access.h"
48 #include "RNA_define.h"
49
50 #include "UI_interface.h"
51 #include "UI_resources.h"
52
53 #include "BLT_translation.h"
54
55 #include "WM_api.h"
56 #include "WM_types.h"
57
58 #include "screen_intern.h"
59
60 /** \name Workspace API
61 *
62 * \brief API for managing workspaces and their data.
63 * \{ */
64
ED_workspace_add(Main * bmain,const char * name)65 WorkSpace *ED_workspace_add(Main *bmain, const char *name)
66 {
67 return BKE_workspace_add(bmain, name);
68 }
69
70 /**
71 * Changes the object mode (if needed) to the one set in \a workspace_new.
72 * Object mode is still stored on object level. In future it should all be workspace level instead.
73 */
workspace_change_update(WorkSpace * workspace_new,const WorkSpace * workspace_old,bContext * C,wmWindowManager * wm)74 static void workspace_change_update(WorkSpace *workspace_new,
75 const WorkSpace *workspace_old,
76 bContext *C,
77 wmWindowManager *wm)
78 {
79 /* needs to be done before changing mode! (to ensure right context) */
80 UNUSED_VARS(workspace_old, workspace_new, C, wm);
81 #if 0
82 Object *ob_act = CTX_data_active_object(C) eObjectMode mode_old = workspace_old->object_mode;
83 eObjectMode mode_new = workspace_new->object_mode;
84
85 if (mode_old != mode_new) {
86 ED_object_mode_set(C, mode_new);
87 }
88 #endif
89 }
90
workspace_change_get_new_layout(Main * bmain,WorkSpace * workspace_new,wmWindow * win)91 static WorkSpaceLayout *workspace_change_get_new_layout(Main *bmain,
92 WorkSpace *workspace_new,
93 wmWindow *win)
94 {
95 WorkSpaceLayout *layout_old = WM_window_get_active_layout(win);
96 WorkSpaceLayout *layout_new;
97
98 /* ED_workspace_duplicate may have stored a layout to activate
99 * once the workspace gets activated. */
100 if (win->workspace_hook->temp_workspace_store) {
101 layout_new = win->workspace_hook->temp_layout_store;
102 }
103 else {
104 layout_new = BKE_workspace_active_layout_for_workspace_get(win->workspace_hook, workspace_new);
105 if (!layout_new) {
106 layout_new = workspace_new->layouts.first;
107 }
108 }
109
110 return ED_workspace_screen_change_ensure_unused_layout(
111 bmain, workspace_new, layout_new, layout_old, win);
112 }
113
114 /**
115 * \brief Change the active workspace.
116 *
117 * Operator call, WM + Window + screen already existed before
118 * Pretty similar to #ED_screen_change since changing workspace also changes screen.
119 *
120 * \warning Do NOT call in area/region queues!
121 * \returns if workspace changing was successful.
122 */
ED_workspace_change(WorkSpace * workspace_new,bContext * C,wmWindowManager * wm,wmWindow * win)123 bool ED_workspace_change(WorkSpace *workspace_new, bContext *C, wmWindowManager *wm, wmWindow *win)
124 {
125 Main *bmain = CTX_data_main(C);
126 WorkSpace *workspace_old = WM_window_get_active_workspace(win);
127 WorkSpaceLayout *layout_new = workspace_change_get_new_layout(bmain, workspace_new, win);
128 bScreen *screen_new = BKE_workspace_layout_screen_get(layout_new);
129 bScreen *screen_old = BKE_workspace_active_screen_get(win->workspace_hook);
130
131 win->workspace_hook->temp_layout_store = NULL;
132 if (workspace_old == workspace_new) {
133 /* Could also return true, everything that needs to be done was done (nothing :P),
134 * but nothing changed */
135 return false;
136 }
137
138 screen_change_prepare(screen_old, screen_new, bmain, C, win);
139
140 if (screen_new == NULL) {
141 return false;
142 }
143
144 BKE_workspace_active_layout_set(win->workspace_hook, win->winid, workspace_new, layout_new);
145 BKE_workspace_active_set(win->workspace_hook, workspace_new);
146
147 /* update screen *after* changing workspace - which also causes the
148 * actual screen change and updates context (including CTX_wm_workspace) */
149 screen_change_update(C, win, screen_new);
150 workspace_change_update(workspace_new, workspace_old, C, wm);
151
152 BLI_assert(CTX_wm_workspace(C) == workspace_new);
153
154 /* Automatic mode switching. */
155 if (workspace_new->object_mode != workspace_old->object_mode) {
156 ED_object_mode_set(C, workspace_new->object_mode);
157 }
158
159 return true;
160 }
161
162 /**
163 * Duplicate a workspace including its layouts. Does not activate the workspace, but
164 * it stores the screen-layout to be activated (BKE_workspace_temp_layout_store)
165 */
ED_workspace_duplicate(WorkSpace * workspace_old,Main * bmain,wmWindow * win)166 WorkSpace *ED_workspace_duplicate(WorkSpace *workspace_old, Main *bmain, wmWindow *win)
167 {
168 WorkSpaceLayout *layout_active_old = BKE_workspace_active_layout_get(win->workspace_hook);
169 WorkSpace *workspace_new = ED_workspace_add(bmain, workspace_old->id.name + 2);
170
171 workspace_new->flags = workspace_old->flags;
172 workspace_new->object_mode = workspace_old->object_mode;
173 workspace_new->order = workspace_old->order;
174 BLI_duplicatelist(&workspace_new->owner_ids, &workspace_old->owner_ids);
175
176 /* TODO(campbell): tools */
177
178 LISTBASE_FOREACH (WorkSpaceLayout *, layout_old, &workspace_old->layouts) {
179 WorkSpaceLayout *layout_new = ED_workspace_layout_duplicate(
180 bmain, workspace_new, layout_old, win);
181
182 if (layout_active_old == layout_old) {
183 win->workspace_hook->temp_layout_store = layout_new;
184 }
185 }
186 return workspace_new;
187 }
188
189 /**
190 * \return if succeeded.
191 */
ED_workspace_delete(WorkSpace * workspace,Main * bmain,bContext * C,wmWindowManager * wm)192 bool ED_workspace_delete(WorkSpace *workspace, Main *bmain, bContext *C, wmWindowManager *wm)
193 {
194 if (BLI_listbase_is_single(&bmain->workspaces)) {
195 return false;
196 }
197
198 ListBase ordered;
199 BKE_id_ordered_list(&ordered, &bmain->workspaces);
200 WorkSpace *prev = NULL, *next = NULL;
201 LISTBASE_FOREACH (LinkData *, link, &ordered) {
202 if (link->data == workspace) {
203 prev = link->prev ? link->prev->data : NULL;
204 next = link->next ? link->next->data : NULL;
205 break;
206 }
207 }
208 BLI_freelistN(&ordered);
209 BLI_assert((prev != NULL) || (next != NULL));
210
211 LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
212 WorkSpace *workspace_active = WM_window_get_active_workspace(win);
213 if (workspace_active == workspace) {
214 ED_workspace_change((prev != NULL) ? prev : next, C, wm, win);
215 }
216 }
217
218 BKE_id_free(bmain, &workspace->id);
219 return true;
220 }
221
222 /**
223 * Some editor data may need to be synced with scene data (3D View camera and layers).
224 * This function ensures data is synced for editors in active layout of \a workspace.
225 */
ED_workspace_scene_data_sync(WorkSpaceInstanceHook * hook,Scene * scene)226 void ED_workspace_scene_data_sync(WorkSpaceInstanceHook *hook, Scene *scene)
227 {
228 bScreen *screen = BKE_workspace_active_screen_get(hook);
229 BKE_screen_view3d_scene_sync(screen, scene);
230 }
231
232 /** \} Workspace API */
233
234 /** \name Workspace Operators
235 *
236 * \{ */
237
workspace_context_get(bContext * C)238 static WorkSpace *workspace_context_get(bContext *C)
239 {
240 ID *id = UI_context_active_but_get_tab_ID(C);
241 if (id && GS(id->name) == ID_WS) {
242 return (WorkSpace *)id;
243 }
244
245 return CTX_wm_workspace(C);
246 }
247
workspace_context_poll(bContext * C)248 static bool workspace_context_poll(bContext *C)
249 {
250 return workspace_context_get(C) != NULL;
251 }
252
workspace_new_exec(bContext * C,wmOperator * UNUSED (op))253 static int workspace_new_exec(bContext *C, wmOperator *UNUSED(op))
254 {
255 Main *bmain = CTX_data_main(C);
256 wmWindow *win = CTX_wm_window(C);
257 WorkSpace *workspace = workspace_context_get(C);
258
259 workspace = ED_workspace_duplicate(workspace, bmain, win);
260
261 WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_SET, workspace);
262
263 return OPERATOR_FINISHED;
264 }
265
WORKSPACE_OT_duplicate(wmOperatorType * ot)266 static void WORKSPACE_OT_duplicate(wmOperatorType *ot)
267 {
268 /* identifiers */
269 ot->name = "New Workspace";
270 ot->description = "Add a new workspace";
271 ot->idname = "WORKSPACE_OT_duplicate";
272
273 /* api callbacks */
274 ot->poll = workspace_context_poll;
275 ot->exec = workspace_new_exec;
276 }
277
workspace_delete_exec(bContext * C,wmOperator * UNUSED (op))278 static int workspace_delete_exec(bContext *C, wmOperator *UNUSED(op))
279 {
280 WorkSpace *workspace = workspace_context_get(C);
281 WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_DELETE, workspace);
282 WM_event_add_notifier(C, NC_WINDOW, NULL);
283
284 return OPERATOR_FINISHED;
285 }
286
WORKSPACE_OT_delete(wmOperatorType * ot)287 static void WORKSPACE_OT_delete(wmOperatorType *ot)
288 {
289 /* identifiers */
290 ot->name = "Delete Workspace";
291 ot->description = "Delete the active workspace";
292 ot->idname = "WORKSPACE_OT_delete";
293
294 /* api callbacks */
295 ot->poll = workspace_context_poll;
296 ot->exec = workspace_delete_exec;
297 }
298
workspace_append_activate_exec(bContext * C,wmOperator * op)299 static int workspace_append_activate_exec(bContext *C, wmOperator *op)
300 {
301 Main *bmain = CTX_data_main(C);
302 char idname[MAX_ID_NAME - 2], filepath[FILE_MAX];
303
304 if (!RNA_struct_property_is_set(op->ptr, "idname") ||
305 !RNA_struct_property_is_set(op->ptr, "filepath")) {
306 return OPERATOR_CANCELLED;
307 }
308 RNA_string_get(op->ptr, "idname", idname);
309 RNA_string_get(op->ptr, "filepath", filepath);
310
311 WorkSpace *appended_workspace = (WorkSpace *)WM_file_append_datablock(
312 bmain, CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C), filepath, ID_WS, idname);
313
314 if (appended_workspace) {
315 /* Set defaults. */
316 BLO_update_defaults_workspace(appended_workspace, NULL);
317
318 /* Reorder to last position. */
319 BKE_id_reorder(&bmain->workspaces, &appended_workspace->id, NULL, true);
320
321 /* Changing workspace changes context. Do delayed! */
322 WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_SET, appended_workspace);
323
324 return OPERATOR_FINISHED;
325 }
326
327 return OPERATOR_CANCELLED;
328 }
329
WORKSPACE_OT_append_activate(wmOperatorType * ot)330 static void WORKSPACE_OT_append_activate(wmOperatorType *ot)
331 {
332 /* identifiers */
333 ot->name = "Append and Activate Workspace";
334 ot->description = "Append a workspace and make it the active one in the current window";
335 ot->idname = "WORKSPACE_OT_append_activate";
336
337 /* api callbacks */
338 ot->exec = workspace_append_activate_exec;
339
340 RNA_def_string(ot->srna,
341 "idname",
342 NULL,
343 MAX_ID_NAME - 2,
344 "Identifier",
345 "Name of the workspace to append and activate");
346 RNA_def_string(ot->srna, "filepath", NULL, FILE_MAX, "Filepath", "Path to the library");
347 }
348
workspace_config_file_read(const char * app_template)349 static WorkspaceConfigFileData *workspace_config_file_read(const char *app_template)
350 {
351 const char *cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, app_template);
352 char startup_file_path[FILE_MAX] = {0};
353
354 if (cfgdir) {
355 BLI_join_dirfile(startup_file_path, sizeof(startup_file_path), cfgdir, BLENDER_STARTUP_FILE);
356 }
357
358 bool has_path = BLI_exists(startup_file_path);
359 return (has_path) ? BKE_blendfile_workspace_config_read(startup_file_path, NULL, 0, NULL) : NULL;
360 }
361
workspace_system_file_read(const char * app_template)362 static WorkspaceConfigFileData *workspace_system_file_read(const char *app_template)
363 {
364 if (app_template == NULL) {
365 return BKE_blendfile_workspace_config_read(
366 NULL, datatoc_startup_blend, datatoc_startup_blend_size, NULL);
367 }
368
369 char template_dir[FILE_MAX];
370 if (!BKE_appdir_app_template_id_search(app_template, template_dir, sizeof(template_dir))) {
371 return NULL;
372 }
373
374 char startup_file_path[FILE_MAX];
375 BLI_join_dirfile(
376 startup_file_path, sizeof(startup_file_path), template_dir, BLENDER_STARTUP_FILE);
377
378 bool has_path = BLI_exists(startup_file_path);
379 return (has_path) ? BKE_blendfile_workspace_config_read(startup_file_path, NULL, 0, NULL) : NULL;
380 }
381
workspace_append_button(uiLayout * layout,wmOperatorType * ot_append,const WorkSpace * workspace,const Main * from_main)382 static void workspace_append_button(uiLayout *layout,
383 wmOperatorType *ot_append,
384 const WorkSpace *workspace,
385 const Main *from_main)
386 {
387 const ID *id = (ID *)workspace;
388 const char *filepath = from_main->name;
389
390 if (strlen(filepath) == 0) {
391 filepath = BLO_EMBEDDED_STARTUP_BLEND;
392 }
393
394 BLI_assert(STREQ(ot_append->idname, "WORKSPACE_OT_append_activate"));
395
396 PointerRNA opptr;
397 uiItemFullO_ptr(
398 layout, ot_append, workspace->id.name + 2, ICON_NONE, NULL, WM_OP_EXEC_DEFAULT, 0, &opptr);
399 RNA_string_set(&opptr, "idname", id->name + 2);
400 RNA_string_set(&opptr, "filepath", filepath);
401 }
402
workspace_add_menu(bContext * UNUSED (C),uiLayout * layout,void * template_v)403 static void workspace_add_menu(bContext *UNUSED(C), uiLayout *layout, void *template_v)
404 {
405 const char *app_template = template_v;
406 bool has_startup_items = false;
407
408 wmOperatorType *ot_append = WM_operatortype_find("WORKSPACE_OT_append_activate", true);
409 WorkspaceConfigFileData *startup_config = workspace_config_file_read(app_template);
410 WorkspaceConfigFileData *builtin_config = workspace_system_file_read(app_template);
411
412 if (startup_config) {
413 LISTBASE_FOREACH (WorkSpace *, workspace, &startup_config->workspaces) {
414 uiLayout *row = uiLayoutRow(layout, false);
415 workspace_append_button(row, ot_append, workspace, startup_config->main);
416 has_startup_items = true;
417 }
418 }
419
420 if (builtin_config) {
421 bool has_title = false;
422
423 LISTBASE_FOREACH (WorkSpace *, workspace, &builtin_config->workspaces) {
424 if (startup_config &&
425 BLI_findstring(&startup_config->workspaces, workspace->id.name, offsetof(ID, name))) {
426 continue;
427 }
428
429 if (!has_title) {
430 if (has_startup_items) {
431 uiItemS(layout);
432 }
433 has_title = true;
434 }
435
436 uiLayout *row = uiLayoutRow(layout, false);
437 workspace_append_button(row, ot_append, workspace, builtin_config->main);
438 }
439 }
440
441 if (startup_config) {
442 BKE_blendfile_workspace_config_data_free(startup_config);
443 }
444 if (builtin_config) {
445 BKE_blendfile_workspace_config_data_free(builtin_config);
446 }
447 }
448
workspace_add_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))449 static int workspace_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
450 {
451 uiPopupMenu *pup = UI_popup_menu_begin(C, op->type->name, ICON_ADD);
452 uiLayout *layout = UI_popup_menu_layout(pup);
453
454 uiItemMenuF(layout, IFACE_("General"), ICON_NONE, workspace_add_menu, NULL);
455
456 ListBase templates;
457 BKE_appdir_app_templates(&templates);
458
459 LISTBASE_FOREACH (LinkData *, link, &templates) {
460 char *template = link->data;
461 char display_name[FILE_MAX];
462
463 BLI_path_to_display_name(display_name, sizeof(display_name), template);
464
465 /* Steals ownership of link data string. */
466 uiItemMenuFN(layout, display_name, ICON_NONE, workspace_add_menu, template);
467 }
468
469 BLI_freelistN(&templates);
470
471 uiItemS(layout);
472 uiItemO(layout,
473 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Duplicate Current"),
474 ICON_DUPLICATE,
475 "WORKSPACE_OT_duplicate");
476
477 UI_popup_menu_end(C, pup);
478
479 return OPERATOR_INTERFACE;
480 }
481
WORKSPACE_OT_add(wmOperatorType * ot)482 static void WORKSPACE_OT_add(wmOperatorType *ot)
483 {
484 /* identifiers */
485 ot->name = "Add Workspace";
486 ot->description =
487 "Add a new workspace by duplicating the current one or appending one "
488 "from the user configuration";
489 ot->idname = "WORKSPACE_OT_add";
490
491 /* api callbacks */
492 ot->invoke = workspace_add_invoke;
493 }
494
workspace_reorder_to_back_exec(bContext * C,wmOperator * UNUSED (op))495 static int workspace_reorder_to_back_exec(bContext *C, wmOperator *UNUSED(op))
496 {
497 Main *bmain = CTX_data_main(C);
498 WorkSpace *workspace = workspace_context_get(C);
499
500 BKE_id_reorder(&bmain->workspaces, &workspace->id, NULL, true);
501 WM_event_add_notifier(C, NC_WINDOW, NULL);
502
503 return OPERATOR_INTERFACE;
504 }
505
WORKSPACE_OT_reorder_to_back(wmOperatorType * ot)506 static void WORKSPACE_OT_reorder_to_back(wmOperatorType *ot)
507 {
508 /* identifiers */
509 ot->name = "Workspace Reorder to Back";
510 ot->description = "Reorder workspace to be first in the list";
511 ot->idname = "WORKSPACE_OT_reorder_to_back";
512
513 /* api callbacks */
514 ot->poll = workspace_context_poll;
515 ot->exec = workspace_reorder_to_back_exec;
516 }
517
workspace_reorder_to_front_exec(bContext * C,wmOperator * UNUSED (op))518 static int workspace_reorder_to_front_exec(bContext *C, wmOperator *UNUSED(op))
519 {
520 Main *bmain = CTX_data_main(C);
521 WorkSpace *workspace = workspace_context_get(C);
522
523 BKE_id_reorder(&bmain->workspaces, &workspace->id, NULL, false);
524 WM_event_add_notifier(C, NC_WINDOW, NULL);
525
526 return OPERATOR_INTERFACE;
527 }
528
WORKSPACE_OT_reorder_to_front(wmOperatorType * ot)529 static void WORKSPACE_OT_reorder_to_front(wmOperatorType *ot)
530 {
531 /* identifiers */
532 ot->name = "Workspace Reorder to Front";
533 ot->description = "Reorder workspace to be first in the list";
534 ot->idname = "WORKSPACE_OT_reorder_to_front";
535
536 /* api callbacks */
537 ot->poll = workspace_context_poll;
538 ot->exec = workspace_reorder_to_front_exec;
539 }
540
ED_operatortypes_workspace(void)541 void ED_operatortypes_workspace(void)
542 {
543 WM_operatortype_append(WORKSPACE_OT_duplicate);
544 WM_operatortype_append(WORKSPACE_OT_delete);
545 WM_operatortype_append(WORKSPACE_OT_add);
546 WM_operatortype_append(WORKSPACE_OT_append_activate);
547 WM_operatortype_append(WORKSPACE_OT_reorder_to_back);
548 WM_operatortype_append(WORKSPACE_OT_reorder_to_front);
549 }
550
551 /** \} Workspace Operators */
552