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