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 }