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