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 }