Prevent crash when switchpanel is not initialised.
[wmaker-crm.git] / src / placement.c
blob1e707094d630eb0da7ac5ceb267c7a0c20c641cb
1 /* placement.c - window and icon placement on screen
3 * Window Maker window manager
5 * Copyright (c) 1997-2003 Alfredo K. Kojima
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "wconfig.h"
24 #include <X11/Xlib.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <limits.h>
30 #include "WindowMaker.h"
31 #include "wcore.h"
32 #include "framewin.h"
33 #include "window.h"
34 #include "icon.h"
35 #include "appicon.h"
36 #include "actions.h"
37 #include "funcs.h"
38 #include "application.h"
39 #include "dock.h"
40 #include "xinerama.h"
41 #include "placement.h"
43 extern WPreferences wPreferences;
45 #define X_ORIGIN WMAX(usableArea.x1,\
46 wPreferences.window_place_origin.x)
48 #define Y_ORIGIN WMAX(usableArea.y1,\
49 wPreferences.window_place_origin.y)
51 /* Returns True if it is an icon and is in this workspace */
52 static Bool
53 iconPosition(WCoreWindow *wcore, int sx1, int sy1, int sx2, int sy2,
54 int workspace, int *retX, int *retY)
56 void *parent;
57 int ok = 0;
59 parent = wcore->descriptor.parent;
61 /* if it is an application icon */
62 if (wcore->descriptor.parent_type == WCLASS_APPICON && !((WAppIcon *) parent)->docked) {
63 *retX = ((WAppIcon *) parent)->x_pos;
64 *retY = ((WAppIcon *) parent)->y_pos;
66 ok = 1;
67 } else if (wcore->descriptor.parent_type == WCLASS_MINIWINDOW &&
68 (((WIcon *) parent)->owner->frame->workspace == workspace
69 || IS_OMNIPRESENT(((WIcon *) parent)->owner)
70 || wPreferences.sticky_icons)
71 && ((WIcon *) parent)->mapped) {
73 *retX = ((WIcon *) parent)->owner->icon_x;
74 *retY = ((WIcon *) parent)->owner->icon_y;
76 ok = 1;
77 } else if (wcore->descriptor.parent_type == WCLASS_WINDOW
78 && ((WWindow *) parent)->flags.icon_moved
79 && (((WWindow *) parent)->frame->workspace == workspace || IS_OMNIPRESENT((WWindow *) parent)
80 || wPreferences.sticky_icons)) {
81 *retX = ((WWindow *) parent)->icon_x;
82 *retY = ((WWindow *) parent)->icon_y;
84 ok = 1;
87 /* Check if it is inside the screen */
88 if (ok) {
89 if (*retX < sx1 - wPreferences.icon_size)
90 ok = 0;
91 else if (*retX > sx2)
92 ok = 0;
94 if (*retY < sy1 - wPreferences.icon_size)
95 ok = 0;
96 else if (*retY > sy2)
97 ok = 0;
100 return ok;
103 void PlaceIcon(WScreen *scr, int *x_ret, int *y_ret, int head)
105 int pf; /* primary axis */
106 int sf; /* secondary axis */
107 int fullW;
108 int fullH;
109 char *map;
110 int pi, si;
111 WCoreWindow *obj;
112 int sx1, sx2, sy1, sy2; /* screen boundary */
113 int sw, sh;
114 int xo, yo;
115 int xs, ys;
116 int x, y;
117 int isize = wPreferences.icon_size;
118 int done = 0;
119 WMBagIterator iter;
120 WArea area = wGetUsableAreaForHead(scr, head, NULL, False);
122 /* Find out screen boundaries. */
124 /* Allows each head to have miniwindows */
125 sx1 = area.x1;
126 sy1 = area.y1;
127 sx2 = area.x2;
128 sy2 = area.y2;
129 sw = sx2 - sx1;
130 sh = sy2 - sy1;
132 sw = isize * (sw / isize);
133 sh = isize * (sh / isize);
134 fullW = (sx2 - sx1) / isize;
135 fullH = (sy2 - sy1) / isize;
137 /* icon yard boundaries */
138 if (wPreferences.icon_yard & IY_VERT) {
139 pf = fullH;
140 sf = fullW;
141 } else {
142 pf = fullW;
143 sf = fullH;
145 if (wPreferences.icon_yard & IY_RIGHT) {
146 xo = sx2 - isize;
147 xs = -1;
148 } else {
149 xo = sx1;
150 xs = 1;
152 if (wPreferences.icon_yard & IY_TOP) {
153 yo = sy1;
154 ys = 1;
155 } else {
156 yo = sy2 - isize;
157 ys = -1;
161 * Create a map with the occupied slots. 1 means the slot is used
162 * or at least partially used.
163 * The slot usage can be optimized by only marking fully used slots
164 * or slots that have most of it covered.
165 * Space usage is worse than the fvwm algorithm (used in the old version)
166 * but complexity is much better (faster) than it.
168 map = wmalloc((sw + 2) * (sh + 2));
170 #define INDEX(x,y) (((y)+1)*(sw+2) + (x) + 1)
172 WM_ETARETI_BAG(scr->stacking_list, obj, iter) {
174 while (obj) {
175 int x, y;
177 if (iconPosition(obj, sx1, sy1, sx2, sy2, scr->current_workspace, &x, &y)) {
178 int xdi, ydi; /* rounded down */
179 int xui, yui; /* rounded up */
181 xdi = x / isize;
182 ydi = y / isize;
183 xui = (x + isize / 2) / isize;
184 yui = (y + isize / 2) / isize;
185 map[INDEX(xdi, ydi)] = 1;
186 map[INDEX(xdi, yui)] = 1;
187 map[INDEX(xui, ydi)] = 1;
188 map[INDEX(xui, yui)] = 1;
190 obj = obj->stacking->under;
193 /* Default position */
194 *x_ret = 0;
195 *y_ret = 0;
197 /* Look for an empty slot */
198 for (si = 0; si < sf; si++) {
199 for (pi = 0; pi < pf; pi++) {
200 if (wPreferences.icon_yard & IY_VERT) {
201 x = xo + xs * (si * isize);
202 y = yo + ys * (pi * isize);
203 } else {
204 x = xo + xs * (pi * isize);
205 y = yo + ys * (si * isize);
207 if (!map[INDEX(x / isize, y / isize)]) {
208 *x_ret = x;
209 *y_ret = y;
210 done = 1;
211 break;
214 if (done)
215 break;
218 wfree(map);
221 /* Computes the intersecting length of two line sections */
222 int calcIntersectionLength(int p1, int l1, int p2, int l2)
224 int isect;
225 int tmp;
227 if (p1 > p2) {
228 tmp = p1;
229 p1 = p2;
230 p2 = tmp;
231 tmp = l1;
232 l1 = l2;
233 l2 = tmp;
236 if (p1 + l1 < p2)
237 isect = 0;
238 else if (p2 + l2 < p1 + l1)
239 isect = l2;
240 else
241 isect = p1 + l1 - p2;
243 return isect;
246 /* Computes the intersecting area of two rectangles */
247 int calcIntersectionArea(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
249 return calcIntersectionLength(x1, w1, x2, w2)
250 * calcIntersectionLength(y1, h1, y2, h2);
253 static int calcSumOfCoveredAreas(WWindow *wwin, int x, int y, int w, int h)
255 int sum_isect = 0;
256 WWindow *test_window;
257 int tw, tx, ty, th;
259 test_window = wwin->screen_ptr->focused_window;
260 for (; test_window != NULL && test_window->prev != NULL;)
261 test_window = test_window->prev;
263 for (; test_window != NULL; test_window = test_window->next) {
264 if (test_window->frame->core->stacking->window_level < WMNormalLevel) {
265 continue;
268 tw = test_window->frame->core->width;
269 th = test_window->frame->core->height;
270 tx = test_window->frame_x;
271 ty = test_window->frame_y;
273 if (test_window->flags.mapped || (test_window->flags.shaded &&
274 test_window->frame->workspace == wwin->screen_ptr->current_workspace &&
275 !(test_window->flags.miniaturized || test_window->flags.hidden))) {
276 sum_isect += calcIntersectionArea(tx, ty, tw, th, x, y, w, h);
280 return sum_isect;
283 static void set_width_height(WWindow *wwin, unsigned int *width, unsigned int *height)
285 if (wwin->frame) {
286 *height += wwin->frame->top_width + wwin->frame->bottom_width;
287 } else {
288 if (HAS_TITLEBAR(wwin))
289 *height += TITLEBAR_HEIGHT;
290 if (HAS_RESIZEBAR(wwin))
291 *height += RESIZEBAR_HEIGHT;
293 if (HAS_BORDER(wwin)) {
294 *height += 2 * wwin->screen_ptr->frame_border_width;
295 *width += 2 * wwin->screen_ptr->frame_border_width;
299 static void
300 smartPlaceWindow(WWindow *wwin, int *x_ret, int *y_ret, unsigned int width,
301 unsigned int height, WArea usableArea)
303 int test_x = 0, test_y = Y_ORIGIN;
304 int from_x, to_x, from_y, to_y;
305 int sx;
306 int min_isect, min_isect_x, min_isect_y;
307 int sum_isect;
309 set_width_height(wwin, &width, &height);
311 sx = X_ORIGIN;
312 min_isect = INT_MAX;
313 min_isect_x = sx;
314 min_isect_y = test_y;
316 while (((test_y + height) < usableArea.y2)) {
317 test_x = sx;
318 while ((test_x + width) < usableArea.x2) {
319 sum_isect = calcSumOfCoveredAreas(wwin, test_x, test_y, width, height);
321 if (sum_isect < min_isect) {
322 min_isect = sum_isect;
323 min_isect_x = test_x;
324 min_isect_y = test_y;
327 test_x += PLACETEST_HSTEP;
329 test_y += PLACETEST_VSTEP;
332 from_x = min_isect_x - PLACETEST_HSTEP + 1;
333 from_x = WMAX(from_x, X_ORIGIN);
334 to_x = min_isect_x + PLACETEST_HSTEP;
335 if (to_x + width > usableArea.x2)
336 to_x = usableArea.x2 - width;
338 from_y = min_isect_y - PLACETEST_VSTEP + 1;
339 from_y = WMAX(from_y, Y_ORIGIN);
340 to_y = min_isect_y + PLACETEST_VSTEP;
341 if (to_y + height > usableArea.y2)
342 to_y = usableArea.y2 - height;
344 for (test_x = from_x; test_x < to_x; test_x++) {
345 for (test_y = from_y; test_y < to_y; test_y++) {
346 sum_isect = calcSumOfCoveredAreas(wwin, test_x, test_y, width, height);
348 if (sum_isect < min_isect) {
349 min_isect = sum_isect;
350 min_isect_x = test_x;
351 min_isect_y = test_y;
356 *x_ret = min_isect_x;
357 *y_ret = min_isect_y;
360 static Bool
361 center_place_window(WWindow *wwin, int *x_ret, int *y_ret,
362 unsigned int width, unsigned int height, WArea usableArea)
364 int swidth, sheight;
366 set_width_height(wwin, &width, &height);
367 swidth = usableArea.x2 - usableArea.x1;
368 sheight = usableArea.y2 - usableArea.y1;
370 if (width > swidth || height > sheight)
371 return False;
373 *x_ret = (usableArea.x1 + usableArea.x2 - width) / 2;
374 *y_ret = (usableArea.y1 + usableArea.y2 - height) / 2;
376 return True;
379 static Bool
380 autoPlaceWindow(WWindow *wwin, int *x_ret, int *y_ret,
381 unsigned int width, unsigned int height, int tryCount, WArea usableArea)
383 WScreen *scr = wwin->screen_ptr;
384 int test_x = 0, test_y = Y_ORIGIN;
385 int loc_ok = False, tw, tx, ty, th;
386 int swidth, sx;
387 WWindow *test_window;
389 set_width_height(wwin, &width, &height);
390 swidth = usableArea.x2 - usableArea.x1;
391 sx = X_ORIGIN;
393 /* this was based on fvwm2's smart placement */
394 while (((test_y + height) < (usableArea.y2 - usableArea.y1)) && !loc_ok) {
395 test_x = sx;
397 while (((test_x + width) < swidth) && (!loc_ok)) {
399 loc_ok = True;
400 test_window = scr->focused_window;
402 while ((test_window != NULL) && (loc_ok == True)) {
404 if (test_window->frame->core->stacking->window_level
405 < WMNormalLevel && tryCount > 0) {
406 test_window = test_window->next;
407 continue;
409 tw = test_window->frame->core->width;
410 th = test_window->frame->core->height;
411 tx = test_window->frame_x;
412 ty = test_window->frame_y;
414 if ((tx < (test_x + width)) && ((tx + tw) > test_x) &&
415 (ty < (test_y + height)) && ((ty + th) > test_y) &&
416 (test_window->flags.mapped ||
417 (test_window->flags.shaded &&
418 test_window->frame->workspace == scr->current_workspace &&
419 !(test_window->flags.miniaturized || test_window->flags.hidden)))) {
421 loc_ok = False;
423 test_window = test_window->next;
426 test_window = scr->focused_window;
428 while ((test_window != NULL) && (loc_ok == True)) {
430 if (test_window->frame->core->stacking->window_level
431 < WMNormalLevel && tryCount > 0) {
432 test_window = test_window->prev;
433 continue;
435 tw = test_window->frame->core->width;
436 th = test_window->frame->core->height;
437 tx = test_window->frame_x;
438 ty = test_window->frame_y;
440 if ((tx < (test_x + width)) && ((tx + tw) > test_x) &&
441 (ty < (test_y + height)) && ((ty + th) > test_y) &&
442 (test_window->flags.mapped ||
443 (test_window->flags.shaded &&
444 test_window->frame->workspace == scr->current_workspace &&
445 !(test_window->flags.miniaturized || test_window->flags.hidden)))) {
447 loc_ok = False;
449 test_window = test_window->prev;
451 if (loc_ok == True) {
452 *x_ret = test_x;
453 *y_ret = test_y;
454 break;
456 test_x += PLACETEST_HSTEP;
458 test_y += PLACETEST_VSTEP;
461 return loc_ok;
464 static void
465 cascadeWindow(WScreen *scr, WWindow *wwin, int *x_ret, int *y_ret,
466 unsigned int width, unsigned int height, int h, WArea usableArea)
468 set_width_height(wwin, &width, &height);
470 *x_ret = h * scr->cascade_index + X_ORIGIN;
471 *y_ret = h * scr->cascade_index + Y_ORIGIN;
473 if (width + *x_ret > usableArea.x2 || height + *y_ret > usableArea.y2) {
474 scr->cascade_index = 0;
475 *x_ret = X_ORIGIN;
476 *y_ret = Y_ORIGIN;
480 static void randomPlaceWindow(WWindow *wwin, int *x_ret, int *y_ret,
481 unsigned int width, unsigned int height, WArea usableArea)
483 int w, h;
485 set_width_height(wwin, &width, &height);
487 w = ((usableArea.x2 - X_ORIGIN) - width);
488 h = ((usableArea.y2 - Y_ORIGIN) - height);
489 if (w < 1)
490 w = 1;
491 if (h < 1)
492 h = 1;
493 *x_ret = X_ORIGIN + rand() % w;
494 *y_ret = Y_ORIGIN + rand() % h;
497 void PlaceWindow(WWindow *wwin, int *x_ret, int *y_ret, unsigned width, unsigned height)
499 WScreen *scr = wwin->screen_ptr;
500 int h = WMFontHeight(scr->title_font)
501 + (wPreferences.window_title_clearance + TITLEBAR_EXTEND_SPACE) * 2;
503 if (h > wPreferences.window_title_max_height)
504 h = wPreferences.window_title_max_height;
506 if (h < wPreferences.window_title_min_height)
507 h = wPreferences.window_title_min_height;
509 WArea usableArea = wGetUsableAreaForHead(scr, wGetHeadForPointerLocation(scr),
510 NULL, True);
512 switch (wPreferences.window_placement) {
513 case WPM_MANUAL:
514 InteractivePlaceWindow(wwin, x_ret, y_ret, width, height);
515 break;
517 case WPM_SMART:
518 smartPlaceWindow(wwin, x_ret, y_ret, width, height, usableArea);
519 break;
521 case WPM_CENTER:
522 if (center_place_window(wwin, x_ret, y_ret, width, height, usableArea))
523 break;
525 case WPM_AUTO:
526 if (autoPlaceWindow(wwin, x_ret, y_ret, width, height, 0, usableArea)) {
527 break;
528 } else if (autoPlaceWindow(wwin, x_ret, y_ret, width, height, 1, usableArea)) {
529 break;
531 /* there isn't a break here, because if we fail, it should fall
532 through to cascade placement, as people who want tiling want
533 automagicness aren't going to want to place their window */
535 case WPM_CASCADE:
536 if (wPreferences.window_placement == WPM_AUTO || wPreferences.window_placement == WPM_CENTER)
537 scr->cascade_index++;
539 cascadeWindow(scr, wwin, x_ret, y_ret, width, height, h, usableArea);
541 if (wPreferences.window_placement == WPM_CASCADE)
542 scr->cascade_index++;
543 break;
545 case WPM_RANDOM:
546 randomPlaceWindow(wwin, x_ret, y_ret, width, height, usableArea);
547 break;
551 * clip to usableArea instead of full screen
552 * this will also take dock/clip etc.. into account
553 * aswell as being xinerama friendly
555 if (*x_ret + width > usableArea.x2)
556 *x_ret = usableArea.x2 - width;
557 if (*x_ret < usableArea.x1)
558 *x_ret = usableArea.x1;
560 if (*y_ret + height > usableArea.y2)
561 *y_ret = usableArea.y2 - height;
562 if (*y_ret < usableArea.y1)
563 *y_ret = usableArea.y1;