Avoid icon change to default on winspector save
[wmaker-crm.git] / src / placement.c
blob3d219d69f3296f6dcf3c2974bc4c99c6dbc2e5bc
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"
42 extern WPreferences wPreferences;
44 #define X_ORIGIN WMAX(usableArea.x1,\
45 wPreferences.window_place_origin.x)
47 #define Y_ORIGIN WMAX(usableArea.y1,\
48 wPreferences.window_place_origin.y)
50 /* interactive window placement is in moveres.c */
51 extern void InteractivePlaceWindow(WWindow *wwin, int *x_ret, int *y_ret,
52 unsigned width, unsigned height);
54 /* Returns True if it is an icon and is in this workspace */
55 static Bool
56 iconPosition(WCoreWindow *wcore, int sx1, int sy1, int sx2, int sy2,
57 int workspace, int *retX, int *retY)
59 void *parent;
60 int ok = 0;
62 parent = wcore->descriptor.parent;
64 /* if it is an application icon */
65 if (wcore->descriptor.parent_type == WCLASS_APPICON && !((WAppIcon *) parent)->docked) {
66 *retX = ((WAppIcon *) parent)->x_pos;
67 *retY = ((WAppIcon *) parent)->y_pos;
69 ok = 1;
70 } else if (wcore->descriptor.parent_type == WCLASS_MINIWINDOW &&
71 (((WIcon *) parent)->owner->frame->workspace == workspace
72 || IS_OMNIPRESENT(((WIcon *) parent)->owner)
73 || wPreferences.sticky_icons)
74 && ((WIcon *) parent)->mapped) {
76 *retX = ((WIcon *) parent)->owner->icon_x;
77 *retY = ((WIcon *) parent)->owner->icon_y;
79 ok = 1;
80 } else if (wcore->descriptor.parent_type == WCLASS_WINDOW
81 && ((WWindow *) parent)->flags.icon_moved
82 && (((WWindow *) parent)->frame->workspace == workspace || IS_OMNIPRESENT((WWindow *) parent)
83 || wPreferences.sticky_icons)) {
84 *retX = ((WWindow *) parent)->icon_x;
85 *retY = ((WWindow *) parent)->icon_y;
87 ok = 1;
90 /* Check if it is inside the screen */
91 if (ok) {
92 if (*retX < sx1 - wPreferences.icon_size)
93 ok = 0;
94 else if (*retX > sx2)
95 ok = 0;
97 if (*retY < sy1 - wPreferences.icon_size)
98 ok = 0;
99 else if (*retY > sy2)
100 ok = 0;
103 return ok;
106 void PlaceIcon(WScreen *scr, int *x_ret, int *y_ret, int head)
108 int pf; /* primary axis */
109 int sf; /* secondary axis */
110 int fullW;
111 int fullH;
112 char *map;
113 int pi, si;
114 WCoreWindow *obj;
115 int sx1, sx2, sy1, sy2; /* screen boundary */
116 int sw, sh;
117 int xo, yo;
118 int xs, ys;
119 int x, y;
120 int isize = wPreferences.icon_size;
121 int done = 0;
122 WMBagIterator iter;
123 WArea area = wGetUsableAreaForHead(scr, head, NULL, False);
125 /* Find out screen boundaries. */
127 /* Allows each head to have miniwindows */
128 sx1 = area.x1;
129 sy1 = area.y1;
130 sx2 = area.x2;
131 sy2 = area.y2;
132 sw = sx2 - sx1;
133 sh = sy2 - sy1;
135 sw = isize * (sw / isize);
136 sh = isize * (sh / isize);
137 fullW = (sx2 - sx1) / isize;
138 fullH = (sy2 - sy1) / isize;
140 /* icon yard boundaries */
141 if (wPreferences.icon_yard & IY_VERT) {
142 pf = fullH;
143 sf = fullW;
144 } else {
145 pf = fullW;
146 sf = fullH;
148 if (wPreferences.icon_yard & IY_RIGHT) {
149 xo = sx2 - isize;
150 xs = -1;
151 } else {
152 xo = sx1;
153 xs = 1;
155 if (wPreferences.icon_yard & IY_TOP) {
156 yo = sy1;
157 ys = 1;
158 } else {
159 yo = sy2 - isize;
160 ys = -1;
164 * Create a map with the occupied slots. 1 means the slot is used
165 * or at least partially used.
166 * The slot usage can be optimized by only marking fully used slots
167 * or slots that have most of it covered.
168 * Space usage is worse than the fvwm algorithm (used in the old version)
169 * but complexity is much better (faster) than it.
171 map = wmalloc((sw + 2) * (sh + 2));
173 #define INDEX(x,y) (((y)+1)*(sw+2) + (x) + 1)
175 WM_ETARETI_BAG(scr->stacking_list, obj, iter) {
177 while (obj) {
178 int x, y;
180 if (iconPosition(obj, sx1, sy1, sx2, sy2, scr->current_workspace, &x, &y)) {
181 int xdi, ydi; /* rounded down */
182 int xui, yui; /* rounded up */
184 xdi = x / isize;
185 ydi = y / isize;
186 xui = (x + isize / 2) / isize;
187 yui = (y + isize / 2) / isize;
188 map[INDEX(xdi, ydi)] = 1;
189 map[INDEX(xdi, yui)] = 1;
190 map[INDEX(xui, ydi)] = 1;
191 map[INDEX(xui, yui)] = 1;
193 obj = obj->stacking->under;
196 /* Default position */
197 *x_ret = 0;
198 *y_ret = 0;
200 /* Look for an empty slot */
201 for (si = 0; si < sf; si++) {
202 for (pi = 0; pi < pf; pi++) {
203 if (wPreferences.icon_yard & IY_VERT) {
204 x = xo + xs * (si * isize);
205 y = yo + ys * (pi * isize);
206 } else {
207 x = xo + xs * (pi * isize);
208 y = yo + ys * (si * isize);
210 if (!map[INDEX(x / isize, y / isize)]) {
211 *x_ret = x;
212 *y_ret = y;
213 done = 1;
214 break;
217 if (done)
218 break;
221 wfree(map);
224 /* Computes the intersecting length of two line sections */
225 int calcIntersectionLength(int p1, int l1, int p2, int l2)
227 int isect;
228 int tmp;
230 if (p1 > p2) {
231 tmp = p1;
232 p1 = p2;
233 p2 = tmp;
234 tmp = l1;
235 l1 = l2;
236 l2 = tmp;
239 if (p1 + l1 < p2)
240 isect = 0;
241 else if (p2 + l2 < p1 + l1)
242 isect = l2;
243 else
244 isect = p1 + l1 - p2;
246 return isect;
249 /* Computes the intersecting area of two rectangles */
250 int calcIntersectionArea(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
252 return calcIntersectionLength(x1, w1, x2, w2)
253 * calcIntersectionLength(y1, h1, y2, h2);
256 static int calcSumOfCoveredAreas(WWindow *wwin, int x, int y, int w, int h)
258 int sum_isect = 0;
259 WWindow *test_window;
260 int tw, tx, ty, th;
262 test_window = wwin->screen_ptr->focused_window;
263 for (; test_window != NULL && test_window->prev != NULL;)
264 test_window = test_window->prev;
266 for (; test_window != NULL; test_window = test_window->next) {
267 if (test_window->frame->core->stacking->window_level < WMNormalLevel) {
268 continue;
271 tw = test_window->frame->core->width;
272 th = test_window->frame->core->height;
273 tx = test_window->frame_x;
274 ty = test_window->frame_y;
276 if (test_window->flags.mapped || (test_window->flags.shaded &&
277 test_window->frame->workspace == wwin->screen_ptr->current_workspace &&
278 !(test_window->flags.miniaturized || test_window->flags.hidden))) {
279 sum_isect += calcIntersectionArea(tx, ty, tw, th, x, y, w, h);
283 return sum_isect;
286 static void set_width_height(WWindow *wwin, unsigned int *width, unsigned int *height)
288 if (wwin->frame) {
289 *height += wwin->frame->top_width + wwin->frame->bottom_width;
290 } else {
291 if (HAS_TITLEBAR(wwin))
292 *height += TITLEBAR_HEIGHT;
293 if (HAS_RESIZEBAR(wwin))
294 *height += RESIZEBAR_HEIGHT;
296 if (HAS_BORDER(wwin)) {
297 *height += 2 * FRAME_BORDER_WIDTH;
298 *width += 2 * FRAME_BORDER_WIDTH;
302 static void
303 smartPlaceWindow(WWindow *wwin, int *x_ret, int *y_ret, unsigned int width,
304 unsigned int height, WArea usableArea)
306 int test_x = 0, test_y = Y_ORIGIN;
307 int from_x, to_x, from_y, to_y;
308 int sx;
309 int min_isect, min_isect_x, min_isect_y;
310 int sum_isect;
312 set_width_height(wwin, &width, &height);
314 sx = X_ORIGIN;
315 min_isect = INT_MAX;
316 min_isect_x = sx;
317 min_isect_y = test_y;
319 while (((test_y + height) < usableArea.y2)) {
320 test_x = sx;
321 while ((test_x + width) < usableArea.x2) {
322 sum_isect = calcSumOfCoveredAreas(wwin, test_x, test_y, width, height);
324 if (sum_isect < min_isect) {
325 min_isect = sum_isect;
326 min_isect_x = test_x;
327 min_isect_y = test_y;
330 test_x += PLACETEST_HSTEP;
332 test_y += PLACETEST_VSTEP;
335 from_x = min_isect_x - PLACETEST_HSTEP + 1;
336 from_x = WMAX(from_x, X_ORIGIN);
337 to_x = min_isect_x + PLACETEST_HSTEP;
338 if (to_x + width > usableArea.x2)
339 to_x = usableArea.x2 - width;
341 from_y = min_isect_y - PLACETEST_VSTEP + 1;
342 from_y = WMAX(from_y, Y_ORIGIN);
343 to_y = min_isect_y + PLACETEST_VSTEP;
344 if (to_y + height > usableArea.y2)
345 to_y = usableArea.y2 - height;
347 for (test_x = from_x; test_x < to_x; test_x++) {
348 for (test_y = from_y; test_y < to_y; test_y++) {
349 sum_isect = calcSumOfCoveredAreas(wwin, test_x, test_y, width, height);
351 if (sum_isect < min_isect) {
352 min_isect = sum_isect;
353 min_isect_x = test_x;
354 min_isect_y = test_y;
359 *x_ret = min_isect_x;
360 *y_ret = min_isect_y;
363 static Bool
364 center_place_window(WWindow *wwin, int *x_ret, int *y_ret,
365 unsigned int width, unsigned int height, WArea usableArea)
367 WScreen *scr = wwin->screen_ptr;
368 int try_x, try_y;
369 int swidth, sheight;
370 WWindow *win;
372 set_width_height(wwin, &width, &height);
373 swidth = usableArea.x2 - usableArea.x1;
374 sheight = usableArea.y2 - usableArea.y1;
376 if (width > swidth || height > sheight)
377 return False;
379 try_x = (usableArea.x1 + usableArea.x2 - width) / 2;
380 try_y = (usableArea.y1 + usableArea.y2 - height) / 2;
382 for (win = scr->focused_window; win != NULL; win = win->next) {
383 int w = win->frame->core->width;
384 int h = win->frame->core->height;
385 int x = win->frame_x;
386 int y = win->frame_y;
388 if ((x < (try_x + width)) && ((x + w) > try_x) &&
389 (y < (try_y + height)) && ((y + h) > try_y) &&
390 (win->flags.mapped ||
391 (win->flags.shaded &&
392 win->frame->workspace == scr->current_workspace &&
393 !(win->flags.miniaturized || win->flags.hidden))))
394 return False;
397 *x_ret = try_x;
398 *y_ret = try_y;
400 return True;
403 static Bool
404 autoPlaceWindow(WWindow *wwin, int *x_ret, int *y_ret,
405 unsigned int width, unsigned int height, int tryCount, WArea usableArea)
407 WScreen *scr = wwin->screen_ptr;
408 int test_x = 0, test_y = Y_ORIGIN;
409 int loc_ok = False, tw, tx, ty, th;
410 int swidth, sx;
411 WWindow *test_window;
413 set_width_height(wwin, &width, &height);
414 swidth = usableArea.x2 - usableArea.x1;
415 sx = X_ORIGIN;
417 /* this was based on fvwm2's smart placement */
418 while (((test_y + height) < (usableArea.y2 - usableArea.y1)) && !loc_ok) {
419 test_x = sx;
421 while (((test_x + width) < swidth) && (!loc_ok)) {
423 loc_ok = True;
424 test_window = scr->focused_window;
426 while ((test_window != NULL) && (loc_ok == True)) {
428 if (test_window->frame->core->stacking->window_level
429 < WMNormalLevel && tryCount > 0) {
430 test_window = test_window->next;
431 continue;
433 tw = test_window->frame->core->width;
434 th = test_window->frame->core->height;
435 tx = test_window->frame_x;
436 ty = test_window->frame_y;
438 if ((tx < (test_x + width)) && ((tx + tw) > test_x) &&
439 (ty < (test_y + height)) && ((ty + th) > test_y) &&
440 (test_window->flags.mapped ||
441 (test_window->flags.shaded &&
442 test_window->frame->workspace == scr->current_workspace &&
443 !(test_window->flags.miniaturized || test_window->flags.hidden)))) {
445 loc_ok = False;
447 test_window = test_window->next;
450 test_window = scr->focused_window;
452 while ((test_window != NULL) && (loc_ok == True)) {
454 if (test_window->frame->core->stacking->window_level
455 < WMNormalLevel && tryCount > 0) {
456 test_window = test_window->prev;
457 continue;
459 tw = test_window->frame->core->width;
460 th = test_window->frame->core->height;
461 tx = test_window->frame_x;
462 ty = test_window->frame_y;
464 if ((tx < (test_x + width)) && ((tx + tw) > test_x) &&
465 (ty < (test_y + height)) && ((ty + th) > test_y) &&
466 (test_window->flags.mapped ||
467 (test_window->flags.shaded &&
468 test_window->frame->workspace == scr->current_workspace &&
469 !(test_window->flags.miniaturized || test_window->flags.hidden)))) {
471 loc_ok = False;
473 test_window = test_window->prev;
475 if (loc_ok == True) {
476 *x_ret = test_x;
477 *y_ret = test_y;
478 break;
480 test_x += PLACETEST_HSTEP;
482 test_y += PLACETEST_VSTEP;
485 return loc_ok;
488 static void
489 cascadeWindow(WScreen *scr, WWindow *wwin, int *x_ret, int *y_ret,
490 unsigned int width, unsigned int height, int h, WArea usableArea)
492 set_width_height(wwin, &width, &height);
494 *x_ret = h * scr->cascade_index + X_ORIGIN;
495 *y_ret = h * scr->cascade_index + Y_ORIGIN;
497 if (width + *x_ret > usableArea.x2 || height + *y_ret > usableArea.y2) {
498 scr->cascade_index = 0;
499 *x_ret = X_ORIGIN;
500 *y_ret = Y_ORIGIN;
504 static void randomPlaceWindow(WWindow *wwin, int *x_ret, int *y_ret,
505 unsigned int width, unsigned int height, WArea usableArea)
507 int w, h;
509 set_width_height(wwin, &width, &height);
511 w = ((usableArea.x2 - X_ORIGIN) - width);
512 h = ((usableArea.y2 - Y_ORIGIN) - height);
513 if (w < 1)
514 w = 1;
515 if (h < 1)
516 h = 1;
517 *x_ret = X_ORIGIN + rand() % w;
518 *y_ret = Y_ORIGIN + rand() % h;
521 void PlaceWindow(WWindow *wwin, int *x_ret, int *y_ret, unsigned width, unsigned height)
523 WScreen *scr = wwin->screen_ptr;
524 int h = WMFontHeight(scr->title_font)
525 + (wPreferences.window_title_clearance + TITLEBAR_EXTEND_SPACE) * 2;
526 int border_width;
528 if (h > wPreferences.window_title_max_height)
529 h = wPreferences.window_title_max_height;
531 if (h < wPreferences.window_title_min_height)
532 h = wPreferences.window_title_min_height;
534 WArea usableArea = wGetUsableAreaForHead(scr, wGetHeadForPointerLocation(scr),
535 NULL, True);
537 switch (wPreferences.window_placement) {
538 case WPM_MANUAL:
539 InteractivePlaceWindow(wwin, x_ret, y_ret, width, height);
540 break;
542 case WPM_SMART:
543 smartPlaceWindow(wwin, x_ret, y_ret, width, height, usableArea);
544 break;
546 case WPM_CENTER:
547 if (center_place_window(wwin, x_ret, y_ret, width, height, usableArea))
548 break;
549 /* fall through to auto placement */
551 case WPM_AUTO:
552 if (autoPlaceWindow(wwin, x_ret, y_ret, width, height, 0, usableArea)) {
553 break;
554 } else if (autoPlaceWindow(wwin, x_ret, y_ret, width, height, 1, usableArea)) {
555 break;
557 /* there isn't a break here, because if we fail, it should fall
558 through to cascade placement, as people who want tiling want
559 automagicness aren't going to want to place their window */
561 case WPM_CASCADE:
562 if (wPreferences.window_placement == WPM_AUTO || wPreferences.window_placement == WPM_CENTER)
563 scr->cascade_index++;
565 cascadeWindow(scr, wwin, x_ret, y_ret, width, height, h, usableArea);
567 if (wPreferences.window_placement == WPM_CASCADE)
568 scr->cascade_index++;
569 break;
571 case WPM_RANDOM:
572 randomPlaceWindow(wwin, x_ret, y_ret, width, height, usableArea);
573 break;
577 * clip to usableArea instead of full screen
578 * this will also take dock/clip etc.. into account
579 * aswell as being xinerama friendly
581 border_width = (HAS_BORDER(wwin)) ? 2 * FRAME_BORDER_WIDTH : 0;
582 if (*x_ret + border_width + width > usableArea.x2)
583 *x_ret = usableArea.x2 - border_width - width;
584 if (*x_ret < usableArea.x1)
585 *x_ret = usableArea.x1;
587 if (*y_ret + border_width + height > usableArea.y2)
588 *y_ret = usableArea.y2 - border_width - height;
589 if (*y_ret < usableArea.y1)
590 *y_ret = usableArea.y1;