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 
23 #include "BLI_listbase.h"
24 #include "BLI_utildefines.h"
25 
26 #include "DNA_screen_types.h"
27 #include "DNA_workspace_types.h"
28 
29 #include "BKE_context.h"
30 #include "BKE_main.h"
31 #include "BKE_screen.h"
32 #include "BKE_workspace.h"
33 
34 #include "WM_api.h"
35 
36 #include "ED_screen.h"
37 
38 #include "screen_intern.h"
39 
40 /**
41  * Empty screen, with 1 dummy area without space-data. Uses window size.
42  */
ED_workspace_layout_add(Main * bmain,WorkSpace * workspace,wmWindow * win,const char * name)43 WorkSpaceLayout *ED_workspace_layout_add(Main *bmain,
44                                          WorkSpace *workspace,
45                                          wmWindow *win,
46                                          const char *name)
47 {
48   bScreen *screen;
49   rcti screen_rect;
50 
51   WM_window_screen_rect_calc(win, &screen_rect);
52   screen = screen_add(bmain, name, &screen_rect);
53 
54   return BKE_workspace_layout_add(bmain, workspace, screen, name);
55 }
56 
ED_workspace_layout_duplicate(Main * bmain,WorkSpace * workspace,const WorkSpaceLayout * layout_old,wmWindow * win)57 WorkSpaceLayout *ED_workspace_layout_duplicate(Main *bmain,
58                                                WorkSpace *workspace,
59                                                const WorkSpaceLayout *layout_old,
60                                                wmWindow *win)
61 {
62   bScreen *screen_old = BKE_workspace_layout_screen_get(layout_old);
63   const char *name = BKE_workspace_layout_name_get(layout_old);
64 
65   WorkSpaceLayout *layout_new = ED_workspace_layout_add(bmain, workspace, win, name);
66   bScreen *screen_new = BKE_workspace_layout_screen_get(layout_new);
67 
68   if (BKE_screen_is_fullscreen_area(screen_old)) {
69     LISTBASE_FOREACH (ScrArea *, area_old, &screen_old->areabase) {
70       if (area_old->full) {
71         ScrArea *area_new = (ScrArea *)screen_new->areabase.first;
72         ED_area_data_copy(area_new, area_old, true);
73         ED_area_tag_redraw(area_new);
74         break;
75       }
76     }
77   }
78   else {
79     screen_data_copy(screen_new, screen_old);
80   }
81 
82   return layout_new;
83 }
84 
workspace_layout_delete_doit(WorkSpace * workspace,WorkSpaceLayout * layout_old,WorkSpaceLayout * layout_new,bContext * C)85 static bool workspace_layout_delete_doit(WorkSpace *workspace,
86                                          WorkSpaceLayout *layout_old,
87                                          WorkSpaceLayout *layout_new,
88                                          bContext *C)
89 {
90   Main *bmain = CTX_data_main(C);
91   wmWindow *win = CTX_wm_window(C);
92   bScreen *screen_new = BKE_workspace_layout_screen_get(layout_new);
93 
94   ED_screen_change(C, screen_new);
95 
96   if (BKE_workspace_active_layout_get(win->workspace_hook) != layout_old) {
97     BKE_workspace_layout_remove(bmain, workspace, layout_old);
98     return true;
99   }
100 
101   return false;
102 }
103 
workspace_layout_set_poll(const WorkSpaceLayout * layout)104 bool workspace_layout_set_poll(const WorkSpaceLayout *layout)
105 {
106   const bScreen *screen = BKE_workspace_layout_screen_get(layout);
107 
108   return ((BKE_screen_is_used(screen) == false) &&
109           /* in typical usage temp screens should have a nonzero winid
110            * (all temp screens should be used, or closed & freed). */
111           (screen->temp == false) && (BKE_screen_is_fullscreen_area(screen) == false) &&
112           (screen->id.name[2] != '.' || !(U.uiflag & USER_HIDE_DOT)));
113 }
114 
workspace_layout_delete_find_new(const WorkSpaceLayout * layout_old)115 static WorkSpaceLayout *workspace_layout_delete_find_new(const WorkSpaceLayout *layout_old)
116 {
117   for (WorkSpaceLayout *layout_new = layout_old->prev; layout_new; layout_new = layout_new->next) {
118     if (workspace_layout_set_poll(layout_new)) {
119       return layout_new;
120     }
121   }
122 
123   for (WorkSpaceLayout *layout_new = layout_old->next; layout_new; layout_new = layout_new->next) {
124     if (workspace_layout_set_poll(layout_new)) {
125       return layout_new;
126     }
127   }
128 
129   return NULL;
130 }
131 
132 /**
133  * \warning Only call outside of area/region loops!
134  * \return true if succeeded.
135  */
ED_workspace_layout_delete(WorkSpace * workspace,WorkSpaceLayout * layout_old,bContext * C)136 bool ED_workspace_layout_delete(WorkSpace *workspace, WorkSpaceLayout *layout_old, bContext *C)
137 {
138   const bScreen *screen_old = BKE_workspace_layout_screen_get(layout_old);
139   WorkSpaceLayout *layout_new;
140 
141   BLI_assert(BLI_findindex(&workspace->layouts, layout_old) != -1);
142 
143   /* don't allow deleting temp fullscreens for now */
144   if (BKE_screen_is_fullscreen_area(screen_old)) {
145     return false;
146   }
147 
148   /* A layout/screen can only be in use by one window at a time, so as
149    * long as we are able to find a layout/screen that is unused, we
150    * can safely assume ours is not in use anywhere an delete it. */
151 
152   layout_new = workspace_layout_delete_find_new(layout_old);
153 
154   if (layout_new) {
155     return workspace_layout_delete_doit(workspace, layout_old, layout_new, C);
156   }
157 
158   return false;
159 }
160 
workspace_change_find_new_layout_cb(const WorkSpaceLayout * layout,void * UNUSED (arg))161 static bool workspace_change_find_new_layout_cb(const WorkSpaceLayout *layout, void *UNUSED(arg))
162 {
163   /* return false to stop the iterator if we've found a layout that can be activated */
164   return workspace_layout_set_poll(layout) ? false : true;
165 }
166 
screen_fullscreen_find_associated_normal_screen(const Main * bmain,bScreen * screen)167 static bScreen *screen_fullscreen_find_associated_normal_screen(const Main *bmain, bScreen *screen)
168 {
169   LISTBASE_FOREACH (bScreen *, screen_iter, &bmain->screens) {
170     if ((screen_iter != screen) && ELEM(screen_iter->state, SCREENMAXIMIZED, SCREENFULL)) {
171       ScrArea *area = screen_iter->areabase.first;
172       if (area && area->full == screen) {
173         return screen_iter;
174       }
175     }
176   }
177 
178   return screen;
179 }
180 
screen_is_used_by_other_window(const wmWindow * win,const bScreen * screen)181 static bool screen_is_used_by_other_window(const wmWindow *win, const bScreen *screen)
182 {
183   return BKE_screen_is_used(screen) && (screen->winid != win->winid);
184 }
185 
186 /**
187  * Make sure there is a non-fullscreen layout to switch to that is not used yet by an other window.
188  * Needed for workspace or screen switching to ensure valid screens.
189  *
190  * \param layout_fallback_base: As last resort, this layout is duplicated and returned.
191  */
ED_workspace_screen_change_ensure_unused_layout(Main * bmain,WorkSpace * workspace,WorkSpaceLayout * layout_new,const WorkSpaceLayout * layout_fallback_base,wmWindow * win)192 WorkSpaceLayout *ED_workspace_screen_change_ensure_unused_layout(
193     Main *bmain,
194     WorkSpace *workspace,
195     WorkSpaceLayout *layout_new,
196     const WorkSpaceLayout *layout_fallback_base,
197     wmWindow *win)
198 {
199   WorkSpaceLayout *layout_temp = layout_new;
200   bScreen *screen_temp = BKE_workspace_layout_screen_get(layout_new);
201 
202   screen_temp = screen_fullscreen_find_associated_normal_screen(bmain, screen_temp);
203   layout_temp = BKE_workspace_layout_find(workspace, screen_temp);
204 
205   if (screen_is_used_by_other_window(win, screen_temp)) {
206     /* Screen is already used, try to find a free one. */
207     layout_temp = BKE_workspace_layout_iter_circular(
208         workspace, layout_new, workspace_change_find_new_layout_cb, NULL, false);
209     screen_temp = layout_temp ? BKE_workspace_layout_screen_get(layout_temp) : NULL;
210 
211     if (!layout_temp || screen_is_used_by_other_window(win, screen_temp)) {
212       /* Fallback solution: duplicate layout. */
213       layout_temp = ED_workspace_layout_duplicate(bmain, workspace, layout_fallback_base, win);
214     }
215   }
216 
217   return layout_temp;
218 }
219 
workspace_layout_cycle_iter_cb(const WorkSpaceLayout * layout,void * UNUSED (arg))220 static bool workspace_layout_cycle_iter_cb(const WorkSpaceLayout *layout, void *UNUSED(arg))
221 {
222   /* return false to stop iterator when we have found a layout to activate */
223   return !workspace_layout_set_poll(layout);
224 }
225 
ED_workspace_layout_cycle(WorkSpace * workspace,const short direction,bContext * C)226 bool ED_workspace_layout_cycle(WorkSpace *workspace, const short direction, bContext *C)
227 {
228   wmWindow *win = CTX_wm_window(C);
229   WorkSpaceLayout *old_layout = BKE_workspace_active_layout_get(win->workspace_hook);
230   const bScreen *old_screen = BKE_workspace_layout_screen_get(old_layout);
231   ScrArea *area = CTX_wm_area(C);
232 
233   if (old_screen->temp || (area && area->full && area->full->temp)) {
234     return false;
235   }
236 
237   BLI_assert(ELEM(direction, 1, -1));
238   WorkSpaceLayout *new_layout = BKE_workspace_layout_iter_circular(workspace,
239                                                                    old_layout,
240                                                                    workspace_layout_cycle_iter_cb,
241                                                                    NULL,
242                                                                    (direction == -1) ? true :
243                                                                                        false);
244 
245   if (new_layout && (old_layout != new_layout)) {
246     bScreen *new_screen = BKE_workspace_layout_screen_get(new_layout);
247 
248     if (area && area->full) {
249       /* return to previous state before switching screens */
250       ED_screen_full_restore(C, area); /* may free screen of old_layout */
251     }
252 
253     ED_screen_change(C, new_screen);
254 
255     return true;
256   }
257 
258   return false;
259 }
260