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