Change to the linux kernel coding style
[wmaker-crm.git] / src / placement.c
1 /* placement.c - window and icon placement on screen
2 *
3 * Window Maker window manager
4 *
5 * Copyright (c) 1997-2003 Alfredo K. Kojima
6 *
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.
11 *
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.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20 * USA.
21 */
22
23 #include "wconfig.h"
24
25 #include <X11/Xlib.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <limits.h>
30
31 #include "WindowMaker.h"
32 #include "wcore.h"
33 #include "framewin.h"
34 #include "window.h"
35 #include "icon.h"
36 #include "appicon.h"
37 #include "actions.h"
38 #include "funcs.h"
39 #include "application.h"
40 #include "appicon.h"
41 #include "dock.h"
42 #include "xinerama.h"
43
44 extern WPreferences wPreferences;
45
46 #define X_ORIGIN WMAX(usableArea.x1,\
47 wPreferences.window_place_origin.x)
48
49 #define Y_ORIGIN WMAX(usableArea.y1,\
50 wPreferences.window_place_origin.y)
51
52 /*
53 * interactive window placement is in moveres.c
54 */
55
56 extern void InteractivePlaceWindow(WWindow * wwin, int *x_ret, int *y_ret, unsigned width, unsigned height);
57
58 /*
59 * Returns True if it is an icon and is in this workspace.
60 */
61 static Bool
62 iconPosition(WCoreWindow * wcore, int sx1, int sy1, int sx2, int sy2, int workspace, int *retX, int *retY)
63 {
64 void *parent;
65 int ok = 0;
66
67 parent = wcore->descriptor.parent;
68
69 /* if it is an application icon */
70 if (wcore->descriptor.parent_type == WCLASS_APPICON && !((WAppIcon *) parent)->docked) {
71 *retX = ((WAppIcon *) parent)->x_pos;
72 *retY = ((WAppIcon *) parent)->y_pos;
73
74 ok = 1;
75 } else if (wcore->descriptor.parent_type == WCLASS_MINIWINDOW &&
76 (((WIcon *) parent)->owner->frame->workspace == workspace
77 || IS_OMNIPRESENT(((WIcon *) parent)->owner)
78 || wPreferences.sticky_icons)
79 && ((WIcon *) parent)->mapped) {
80
81 *retX = ((WIcon *) parent)->owner->icon_x;
82 *retY = ((WIcon *) parent)->owner->icon_y;
83
84 ok = 1;
85 } else if (wcore->descriptor.parent_type == WCLASS_WINDOW
86 && ((WWindow *) parent)->flags.icon_moved
87 && (((WWindow *) parent)->frame->workspace == workspace || IS_OMNIPRESENT((WWindow *) parent)
88 || wPreferences.sticky_icons)) {
89 *retX = ((WWindow *) parent)->icon_x;
90 *retY = ((WWindow *) parent)->icon_y;
91
92 ok = 1;
93 }
94
95 /*
96 * Check if it is inside the screen.
97 */
98 if (ok) {
99 if (*retX < sx1 - wPreferences.icon_size)
100 ok = 0;
101 else if (*retX > sx2)
102 ok = 0;
103
104 if (*retY < sy1 - wPreferences.icon_size)
105 ok = 0;
106 else if (*retY > sy2)
107 ok = 0;
108 }
109
110 return ok;
111 }
112
113 void PlaceIcon(WScreen * scr, int *x_ret, int *y_ret, int head)
114 {
115 int pf; /* primary axis */
116 int sf; /* secondary axis */
117 int fullW;
118 int fullH;
119 char *map;
120 int pi, si;
121 WCoreWindow *obj;
122 int sx1, sx2, sy1, sy2; /* screen boundary */
123 int sw, sh;
124 int xo, yo;
125 int xs, ys;
126 int x, y;
127 int isize = wPreferences.icon_size;
128 int done = 0;
129 WMBagIterator iter;
130 WArea area = wGetUsableAreaForHead(scr, head, NULL, False);
131
132 /* Find out screen boundaries. */
133
134 /* Allows each head to have miniwindows */
135
136 sx1 = area.x1;
137 sy1 = area.y1;
138 sx2 = area.x2;
139 sy2 = area.y2;
140 sw = sx2 - sx1;
141 sh = sy2 - sy1;
142
143 #if 0
144 if (scr->dock) {
145 if (scr->dock->on_right_side)
146 sx2 -= isize + DOCK_EXTRA_SPACE;
147 else
148 sx1 += isize + DOCK_EXTRA_SPACE;
149 }
150 #endif
151
152 sw = isize * (sw / isize);
153 sh = isize * (sh / isize);
154 fullW = (sx2 - sx1) / isize;
155 fullH = (sy2 - sy1) / isize;
156
157 /* icon yard boundaries */
158 if (wPreferences.icon_yard & IY_VERT) {
159 pf = fullH;
160 sf = fullW;
161 } else {
162 pf = fullW;
163 sf = fullH;
164 }
165 if (wPreferences.icon_yard & IY_RIGHT) {
166 xo = sx2 - isize;
167 xs = -1;
168 } else {
169 xo = sx1;
170 xs = 1;
171 }
172 if (wPreferences.icon_yard & IY_TOP) {
173 yo = sy1;
174 ys = 1;
175 } else {
176 yo = sy2 - isize;
177 ys = -1;
178 }
179
180 /*
181 * Create a map with the occupied slots. 1 means the slot is used
182 * or at least partially used.
183 * The slot usage can be optimized by only marking fully used slots
184 * or slots that have most of it covered.
185 * Space usage is worse than the fvwm algorithm (used in the old version)
186 * but complexity is much better (faster) than it.
187 */
188 map = wmalloc((sw + 2) * (sh + 2));
189 memset(map, 0, (sw + 2) * (sh + 2));
190
191 #define INDEX(x,y) (((y)+1)*(sw+2) + (x) + 1)
192
193 WM_ETARETI_BAG(scr->stacking_list, obj, iter) {
194
195 while (obj) {
196 int x, y;
197
198 if (iconPosition(obj, sx1, sy1, sx2, sy2, scr->current_workspace, &x, &y)) {
199 int xdi, ydi; /* rounded down */
200 int xui, yui; /* rounded up */
201
202 xdi = x / isize;
203 ydi = y / isize;
204 xui = (x + isize / 2) / isize;
205 yui = (y + isize / 2) / isize;
206 map[INDEX(xdi, ydi)] = 1;
207 map[INDEX(xdi, yui)] = 1;
208 map[INDEX(xui, ydi)] = 1;
209 map[INDEX(xui, yui)] = 1;
210 }
211 obj = obj->stacking->under;
212 }
213 }
214 /*
215 * Default position
216 */
217 *x_ret = 0;
218 *y_ret = 0;
219
220 /*
221 * Look for an empty slot
222 */
223 for (si = 0; si < sf; si++) {
224 for (pi = 0; pi < pf; pi++) {
225 if (wPreferences.icon_yard & IY_VERT) {
226 x = xo + xs * (si * isize);
227 y = yo + ys * (pi * isize);
228 } else {
229 x = xo + xs * (pi * isize);
230 y = yo + ys * (si * isize);
231 }
232 if (!map[INDEX(x / isize, y / isize)]) {
233 *x_ret = x;
234 *y_ret = y;
235 done = 1;
236 break;
237 }
238 }
239 if (done)
240 break;
241 }
242
243 wfree(map);
244 }
245
246 /*
247 * This function calculates the length of the intersection of two
248 * line sections. (Hey, is that english?)
249 */
250 static int calcIntersectionLength(int p1, int l1, int p2, int l2)
251 {
252 int isect;
253 int tmp;
254
255 if (p1 > p2) {
256 tmp = p1;
257 p1 = p2;
258 p2 = tmp;
259 tmp = l1;
260 l1 = l2;
261 l2 = tmp;
262 }
263
264 if (p1 + l1 < p2)
265 isect = 0;
266 else if (p2 + l2 < p1 + l1)
267 isect = l2;
268 else
269 isect = p1 + l1 - p2;
270
271 return isect;
272 }
273
274 /*
275 * This function calculates the area of the intersection of two rectangles.
276 */
277
278 int calcIntersectionArea(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
279 {
280 return calcIntersectionLength(x1, w1, x2, w2)
281 * calcIntersectionLength(y1, h1, y2, h2);
282 }
283
284 static int calcSumOfCoveredAreas(WWindow * wwin, int x, int y, int w, int h)
285 {
286 int sum_isect = 0;
287 WWindow *test_window;
288 int tw, tx, ty, th;
289
290 test_window = wwin->screen_ptr->focused_window;
291 for (; test_window != NULL && test_window->prev != NULL;)
292 test_window = test_window->prev;
293
294 for (; test_window != NULL; test_window = test_window->next) {
295 if (test_window->frame->core->stacking->window_level < WMNormalLevel) {
296 continue;
297 }
298 #if 0
299 tw = test_window->client.width;
300 if (test_window->flags.shaded)
301 th = test_window->frame->top_width;
302 else
303 th = test_window->client.height + extra_height;
304 #else
305 tw = test_window->frame->core->width;
306 th = test_window->frame->core->height;
307 #endif
308 tx = test_window->frame_x;
309 ty = test_window->frame_y;
310
311 if (test_window->flags.mapped ||
312 (test_window->flags.shaded &&
313 !(test_window->flags.miniaturized || test_window->flags.hidden))) {
314 sum_isect += calcIntersectionArea(tx, ty, tw, th, x, y, w, h);
315 }
316 }
317
318 return sum_isect;
319 }
320
321 static void
322 smartPlaceWindow(WWindow * wwin, int *x_ret, int *y_ret, unsigned int width, unsigned int height, WArea usableArea)
323 {
324 int test_x = 0, test_y = Y_ORIGIN;
325 int from_x, to_x, from_y, to_y;
326 int sx;
327 int min_isect, min_isect_x, min_isect_y;
328 int sum_isect;
329
330 if (wwin->frame) {
331 height += wwin->frame->top_width + wwin->frame->bottom_width;
332 } else {
333 if (HAS_TITLEBAR(wwin))
334 height += 18;
335 if (HAS_RESIZEBAR(wwin))
336 height += 8;
337 }
338 if (HAS_BORDER(wwin)) {
339 height += 2;
340 width += 2;
341 }
342 sx = X_ORIGIN;
343
344 min_isect = INT_MAX;
345 min_isect_x = sx;
346 min_isect_y = test_y;
347
348 while (((test_y + height) < usableArea.y2)) {
349 test_x = sx;
350 while ((test_x + width) < usableArea.x2) {
351 sum_isect = calcSumOfCoveredAreas(wwin, test_x, test_y, width, height);
352
353 if (sum_isect < min_isect) {
354 min_isect = sum_isect;
355 min_isect_x = test_x;
356 min_isect_y = test_y;
357 }
358
359 test_x += PLACETEST_HSTEP;
360 }
361 test_y += PLACETEST_VSTEP;
362 }
363
364 from_x = min_isect_x - PLACETEST_HSTEP + 1;
365 from_x = WMAX(from_x, X_ORIGIN);
366 to_x = min_isect_x + PLACETEST_HSTEP;
367 if (to_x + width > usableArea.x2)
368 to_x = usableArea.x2 - width;
369
370 from_y = min_isect_y - PLACETEST_VSTEP + 1;
371 from_y = WMAX(from_y, Y_ORIGIN);
372 to_y = min_isect_y + PLACETEST_VSTEP;
373 if (to_y + height > usableArea.y2)
374 to_y = usableArea.y2 - height;
375
376 for (test_x = from_x; test_x < to_x; test_x++) {
377 for (test_y = from_y; test_y < to_y; test_y++) {
378 sum_isect = calcSumOfCoveredAreas(wwin, test_x, test_y, width, height);
379
380 if (sum_isect < min_isect) {
381 min_isect = sum_isect;
382 min_isect_x = test_x;
383 min_isect_y = test_y;
384 }
385 }
386 }
387
388 *x_ret = min_isect_x;
389 *y_ret = min_isect_y;
390 }
391
392 static Bool
393 autoPlaceWindow(WWindow * wwin, int *x_ret, int *y_ret,
394 unsigned int width, unsigned int height, int tryCount, WArea usableArea)
395 {
396 WScreen *scr = wwin->screen_ptr;
397 int test_x = 0, test_y = Y_ORIGIN;
398 int loc_ok = False, tw, tx, ty, th;
399 int swidth, sx;
400 WWindow *test_window;
401
402 if (wwin->frame) {
403 height += wwin->frame->top_width + wwin->frame->bottom_width;
404 } else {
405 if (HAS_TITLEBAR(wwin))
406 height += 18;
407 if (HAS_RESIZEBAR(wwin))
408 height += 8;
409 }
410 if (HAS_BORDER(wwin)) {
411 height += 2;
412 width += 2;
413 }
414
415 swidth = usableArea.x2 - usableArea.x1;
416 sx = X_ORIGIN;
417
418 /* this was based on fvwm2's smart placement */
419
420 while (((test_y + height) < (usableArea.y2 - usableArea.y1)) && !loc_ok) {
421 test_x = sx;
422
423 while (((test_x + width) < swidth) && (!loc_ok)) {
424
425 loc_ok = True;
426 test_window = scr->focused_window;
427
428 while ((test_window != NULL) && (loc_ok == True)) {
429
430 if (test_window->frame->core->stacking->window_level
431 < WMNormalLevel && tryCount > 0) {
432 test_window = test_window->next;
433 continue;
434 }
435 #if 0
436 tw = test_window->client.width;
437 if (test_window->flags.shaded)
438 th = test_window->frame->top_width;
439 else
440 th = test_window->client.height + extra_height;
441 #else
442 tw = test_window->frame->core->width;
443 th = test_window->frame->core->height;
444 #endif
445 tx = test_window->frame_x;
446 ty = test_window->frame_y;
447
448 if ((tx < (test_x + width)) && ((tx + tw) > test_x) &&
449 (ty < (test_y + height)) && ((ty + th) > test_y) &&
450 (test_window->flags.mapped ||
451 (test_window->flags.shaded &&
452 test_window->frame->workspace == scr->current_workspace &&
453 !(test_window->flags.miniaturized || test_window->flags.hidden)))) {
454
455 loc_ok = False;
456 }
457 test_window = test_window->next;
458 }
459
460 test_window = scr->focused_window;
461
462 while ((test_window != NULL) && (loc_ok == True)) {
463
464 if (test_window->frame->core->stacking->window_level
465 < WMNormalLevel && tryCount > 0) {
466 test_window = test_window->prev;
467 continue;
468 }
469 #if 0
470 tw = test_window->client.width;
471 if (test_window->flags.shaded)
472 th = test_window->frame->top_width;
473 else
474 th = test_window->client.height + extra_height;
475 #else
476 tw = test_window->frame->core->width;
477 th = test_window->frame->core->height;
478 #endif
479 tx = test_window->frame_x;
480 ty = test_window->frame_y;
481
482 if ((tx < (test_x + width)) && ((tx + tw) > test_x) &&
483 (ty < (test_y + height)) && ((ty + th) > test_y) &&
484 (test_window->flags.mapped ||
485 (test_window->flags.shaded &&
486 test_window->frame->workspace == scr->current_workspace &&
487 !(test_window->flags.miniaturized || test_window->flags.hidden)))) {
488
489 loc_ok = False;
490 }
491 test_window = test_window->prev;
492 }
493 if (loc_ok == True) {
494 *x_ret = test_x;
495 *y_ret = test_y;
496 break;
497 }
498 test_x += PLACETEST_HSTEP;
499 }
500 test_y += PLACETEST_VSTEP;
501 }
502
503 return loc_ok;
504 }
505
506 static void
507 cascadeWindow(WScreen * scr, WWindow * wwin, int *x_ret, int *y_ret,
508 unsigned int width, unsigned int height, int h, WArea usableArea)
509 {
510 if (wwin->frame) {
511 height += wwin->frame->top_width + wwin->frame->bottom_width;
512 } else {
513 if (HAS_TITLEBAR(wwin))
514 height += 18;
515 if (HAS_RESIZEBAR(wwin))
516 height += 8;
517 }
518 if (HAS_BORDER(wwin)) {
519 height += 2;
520 width += 2;
521 }
522
523 *x_ret = h * scr->cascade_index + X_ORIGIN;
524 *y_ret = h * scr->cascade_index + Y_ORIGIN;
525
526 if (width + *x_ret > usableArea.x2 || height + *y_ret > usableArea.y2) {
527 scr->cascade_index = 0;
528 *x_ret = X_ORIGIN;
529 *y_ret = Y_ORIGIN;
530 }
531 }
532
533 static void
534 randomPlaceWindow(WScreen * scr, WWindow * wwin, int *x_ret, int *y_ret,
535 unsigned int width, unsigned int height, WArea usableArea)
536 {
537 int w, h;
538
539 if (wwin->frame) {
540 height += wwin->frame->top_width + wwin->frame->bottom_width;
541 } else {
542 if (HAS_TITLEBAR(wwin))
543 height += 18;
544 if (HAS_RESIZEBAR(wwin))
545 height += 8;
546 }
547 if (HAS_BORDER(wwin)) {
548 height += 2;
549 width += 2;
550 }
551
552 w = ((usableArea.x2 - X_ORIGIN) - width);
553 h = ((usableArea.y2 - Y_ORIGIN) - height);
554 if (w < 1)
555 w = 1;
556 if (h < 1)
557 h = 1;
558 *x_ret = X_ORIGIN + rand() % w;
559 *y_ret = Y_ORIGIN + rand() % h;
560 }
561
562 void PlaceWindow(WWindow * wwin, int *x_ret, int *y_ret, unsigned width, unsigned height)
563 {
564 WScreen *scr = wwin->screen_ptr;
565 int h = WMFontHeight(scr->title_font) + (wPreferences.window_title_clearance + TITLEBAR_EXTEND_SPACE) * 2;
566 WArea usableArea = wGetUsableAreaForHead(scr,
567 wGetHeadForPointerLocation(scr),
568 NULL, True);
569
570 switch (wPreferences.window_placement) {
571 case WPM_MANUAL:
572 InteractivePlaceWindow(wwin, x_ret, y_ret, width, height);
573 break;
574
575 case WPM_SMART:
576 smartPlaceWindow(wwin, x_ret, y_ret, width, height, usableArea);
577 break;
578
579 case WPM_AUTO:
580 if (autoPlaceWindow(wwin, x_ret, y_ret, width, height, 0, usableArea)) {
581 break;
582 } else if (autoPlaceWindow(wwin, x_ret, y_ret, width, height, 1, usableArea)) {
583 break;
584 }
585 /* there isn't a break here, because if we fail, it should fall
586 through to cascade placement, as people who want tiling want
587 automagicness aren't going to want to place their window */
588
589 case WPM_CASCADE:
590 if (wPreferences.window_placement == WPM_AUTO)
591 scr->cascade_index++;
592
593 cascadeWindow(scr, wwin, x_ret, y_ret, width, height, h, usableArea);
594
595 if (wPreferences.window_placement == WPM_CASCADE)
596 scr->cascade_index++;
597 break;
598
599 case WPM_RANDOM:
600 randomPlaceWindow(scr, wwin, x_ret, y_ret, width, height, usableArea);
601 break;
602
603 #ifdef DEBUG
604 default:
605 puts("Invalid window placement!!!");
606 *x_ret = 0;
607 *y_ret = 0;
608 #endif
609 }
610
611 /*
612 * clip to usableArea instead of full screen
613 * this will also take dock/clip etc.. into account
614 * aswell as being xinerama friendly
615 */
616 if (*x_ret + width > usableArea.x2)
617 *x_ret = usableArea.x2 - width;
618 if (*x_ret < usableArea.x1)
619 *x_ret = usableArea.x1;
620
621 if (*y_ret + height > usableArea.y2)
622 *y_ret = usableArea.y2 - height;
623 if (*y_ret < usableArea.y1)
624 *y_ret = usableArea.y1;
625 }