no pointless using of the comma operator
[mikachu/openbox.git] / openbox / focus_cycle.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    focus_cycle.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
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    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "focus_cycle.h"
21 #include "focus_cycle_indicator.h"
22 #include "focus_cycle_popup.h"
23 #include "client.h"
24 #include "frame.h"
25 #include "focus.h"
26 #include "screen.h"
27 #include "openbox.h"
28 #include "debug.h"
29 #include "group.h"
30
31 #include <X11/Xlib.h>
32 #include <glib.h>
33
34 ObClient       *focus_cycle_target = NULL;
35 static gboolean focus_cycle_iconic_windows;
36 static gboolean focus_cycle_all_desktops;
37 static gboolean focus_cycle_dock_windows;
38 static gboolean focus_cycle_desktop_windows;
39
40 static gboolean  focus_target_has_siblings  (ObClient *ft,
41                                              gboolean iconic_windows,
42                                              gboolean all_desktops);
43 static ObClient *focus_find_directional    (ObClient *c,
44                                             ObDirection dir,
45                                             gboolean dock_windows,
46                                             gboolean desktop_windows);
47 static ObClient *focus_find_directional    (ObClient *c,
48                                             ObDirection dir,
49                                             gboolean dock_windows,
50                                             gboolean desktop_windows);
51
52 void focus_cycle_startup(gboolean reconfig)
53 {
54     if (reconfig) return;
55 }
56
57 void focus_cycle_shutdown(gboolean reconfig)
58 {
59     if (reconfig) return;
60 }
61
62 void focus_cycle_stop(ObClient *ifclient)
63 {
64     /* stop focus cycling if the given client is a valid focus target,
65        and so the cycling is being disrupted */
66     if (focus_cycle_target && ifclient &&
67         focus_cycle_target_valid(ifclient,
68                                  focus_cycle_iconic_windows,
69                                  focus_cycle_all_desktops,
70                                  focus_cycle_dock_windows,
71                                  focus_cycle_desktop_windows))
72     {
73         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
74         focus_directional_cycle(0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
75     }
76 }
77
78 /*! Returns if a focus target has valid group siblings that can be cycled
79   to in its place */
80 static gboolean focus_target_has_siblings(ObClient *ft,
81                                           gboolean iconic_windows,
82                                           gboolean all_desktops)
83                                                          
84 {
85     GSList *it;
86
87     if (!ft->group) return FALSE;
88
89     for (it = ft->group->members; it; it = g_slist_next(it)) {
90         ObClient *c = it->data;
91         /* check that it's not a helper window to avoid infinite recursion */
92         if (c != ft && !client_helper(c) &&
93             focus_cycle_target_valid(c, iconic_windows, all_desktops, FALSE,
94                                      FALSE))
95         {
96             return TRUE;
97         }
98     }
99     return FALSE;
100 }
101
102 gboolean focus_cycle_target_valid(ObClient *ft,
103                                   gboolean iconic_windows,
104                                   gboolean all_desktops,
105                                   gboolean dock_windows,
106                                   gboolean desktop_windows)
107 {
108     gboolean ok = FALSE;
109
110     /* it's on this desktop unless you want all desktops.
111
112        do this check first because it will usually filter out the most
113        windows */
114     ok = (all_desktops || ft->desktop == screen_desktop ||
115           ft->desktop == DESKTOP_ALL);
116
117     /* the window can receive focus somehow */
118     ok = ok && (ft->can_focus || ft->focus_notify);
119
120     /* the window is not iconic, or we're allowed to go to iconic ones */
121     ok = ok && (iconic_windows || !ft->iconic);
122
123     /* it's the right type of window */
124     if (dock_windows || desktop_windows)
125         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
126                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
127     /* modal windows are important and can always get focus if they are
128        visible and stuff, so don't change 'ok' based on their type */ 
129     else if (!ft->modal)
130         /* normal non-helper windows are valid targets */
131         ok = ok &&
132             ((client_normal(ft) && !client_helper(ft))
133              ||
134              /* helper windows are valid targets it... */
135              (client_helper(ft) &&
136               /* ...a window in its group already has focus ... */
137               ((focus_client && ft->group == focus_client->group) ||
138                /* ... or if there are no other windows in its group 
139                   that can be cycled to instead */
140                !focus_target_has_siblings(ft, iconic_windows, all_desktops))));
141
142     /* it's not set to skip the taskbar (unless it is a type that would be
143        expected to set this hint, or modal) */
144     ok = ok && ((ft->type == OB_CLIENT_TYPE_DOCK ||
145                  ft->type == OB_CLIENT_TYPE_DESKTOP ||
146                  ft->type == OB_CLIENT_TYPE_TOOLBAR ||
147                  ft->type == OB_CLIENT_TYPE_MENU ||
148                  ft->type == OB_CLIENT_TYPE_UTILITY) ||
149                 ft->modal ||
150                 !ft->skip_taskbar);
151
152     /* it's not going to just send focus off somewhere else (modal window),
153        unless that modal window is not one of our valid targets, then let
154        you choose this window and bring the modal one here */
155     {
156         ObClient *cft = client_focus_target(ft);
157         ok = ok && (ft == cft || !focus_cycle_target_valid(cft,
158                                                            iconic_windows,
159                                                            all_desktops,
160                                                            dock_windows,
161                                                            desktop_windows));
162     }
163
164     return ok;
165 }
166
167 void focus_cycle(gboolean forward, gboolean all_desktops,
168                  gboolean dock_windows, gboolean desktop_windows,
169                  gboolean linear, gboolean interactive,
170                  gboolean dialog, gboolean done, gboolean cancel)
171 {
172     static ObClient *t = NULL;
173     static GList *order = NULL;
174     GList *it, *start, *list;
175     ObClient *ft = NULL;
176
177     if (interactive) {
178         if (cancel) {
179             focus_cycle_target = NULL;
180             goto done_cycle;
181         } else if (done)
182             goto done_cycle;
183
184         if (!focus_order)
185             goto done_cycle;
186
187         if (linear) list = client_list;
188         else        list = focus_order;
189     } else {
190         if (!focus_order)
191             goto done_cycle;
192         list = client_list;
193     }
194
195
196     if (focus_cycle_target == NULL) {
197         focus_cycle_iconic_windows = TRUE;
198         focus_cycle_all_desktops = all_desktops;
199         focus_cycle_dock_windows = dock_windows;
200         focus_cycle_desktop_windows = desktop_windows;
201         start = it = g_list_find(list, focus_client);
202     } else
203         start = it = g_list_find(list, focus_cycle_target);
204
205     if (!start) /* switched desktops or something? */
206         start = it = forward ? g_list_last(list) : g_list_first(list);
207     if (!start) goto done_cycle;
208
209     do {
210         if (forward) {
211             it = it->next;
212             if (it == NULL) it = g_list_first(list);
213         } else {
214             it = it->prev;
215             if (it == NULL) it = g_list_last(list);
216         }
217         ft = it->data;
218         if (focus_cycle_target_valid(ft,
219                                      focus_cycle_iconic_windows,
220                                      focus_cycle_all_desktops,
221                                      focus_cycle_dock_windows,
222                                      focus_cycle_desktop_windows))
223         {
224             if (interactive) {
225                 if (ft != focus_cycle_target) { /* prevents flicker */
226                     focus_cycle_target = ft;
227                     focus_cycle_draw_indicator(ft);
228                 }
229                 if (dialog)
230                     /* same arguments as focus_target_valid */
231                     focus_cycle_popup_show(ft,
232                                            focus_cycle_iconic_windows,
233                                            focus_cycle_all_desktops,
234                                            focus_cycle_dock_windows,
235                                            focus_cycle_desktop_windows);
236                 return;
237             } else if (ft != focus_cycle_target) {
238                 focus_cycle_target = ft;
239                 done = TRUE;
240                 break;
241             }
242         }
243     } while (it != start);
244
245 done_cycle:
246     if (done && focus_cycle_target)
247         client_activate(focus_cycle_target, FALSE, TRUE);
248
249     t = NULL;
250     focus_cycle_target = NULL;
251     g_list_free(order);
252     order = NULL;
253
254     if (interactive) {
255         focus_cycle_draw_indicator(NULL);
256         focus_cycle_popup_hide();
257     }
258
259     return;
260 }
261
262 /* this be mostly ripped from fvwm */
263 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
264                                         gboolean dock_windows,
265                                         gboolean desktop_windows) 
266 {
267     gint my_cx, my_cy, his_cx, his_cy;
268     gint offset = 0;
269     gint distance = 0;
270     gint score, best_score;
271     ObClient *best_client, *cur;
272     GList *it;
273
274     if (!client_list)
275         return NULL;
276
277     /* first, find the centre coords of the currently focused window */
278     my_cx = c->frame->area.x + c->frame->area.width / 2;
279     my_cy = c->frame->area.y + c->frame->area.height / 2;
280
281     best_score = -1;
282     best_client = c;
283
284     for (it = g_list_first(client_list); it; it = g_list_next(it)) {
285         cur = it->data;
286
287         /* the currently selected window isn't interesting */
288         if (cur == c)
289             continue;
290         if (!focus_cycle_target_valid(it->data, FALSE, FALSE, dock_windows,
291                                       desktop_windows))
292             continue;
293
294         /* find the centre coords of this window, from the
295          * currently focused window's point of view */
296         his_cx = (cur->frame->area.x - my_cx)
297             + cur->frame->area.width / 2;
298         his_cy = (cur->frame->area.y - my_cy)
299             + cur->frame->area.height / 2;
300
301         if (dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
302             dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST)
303         {
304             gint tx;
305             /* Rotate the diagonals 45 degrees counterclockwise.
306              * To do this, multiply the matrix /+h +h\ with the
307              * vector (x y).                   \-h +h/
308              * h = sqrt(0.5). We can set h := 1 since absolute
309              * distance doesn't matter here. */
310             tx = his_cx + his_cy;
311             his_cy = -his_cx + his_cy;
312             his_cx = tx;
313         }
314
315         switch (dir) {
316         case OB_DIRECTION_NORTH:
317         case OB_DIRECTION_SOUTH:
318         case OB_DIRECTION_NORTHEAST:
319         case OB_DIRECTION_SOUTHWEST:
320             offset = (his_cx < 0) ? -his_cx : his_cx;
321             distance = ((dir == OB_DIRECTION_NORTH ||
322                          dir == OB_DIRECTION_NORTHEAST) ?
323                         -his_cy : his_cy);
324             break;
325         case OB_DIRECTION_EAST:
326         case OB_DIRECTION_WEST:
327         case OB_DIRECTION_SOUTHEAST:
328         case OB_DIRECTION_NORTHWEST:
329             offset = (his_cy < 0) ? -his_cy : his_cy;
330             distance = ((dir == OB_DIRECTION_WEST ||
331                          dir == OB_DIRECTION_NORTHWEST) ?
332                         -his_cx : his_cx);
333             break;
334         }
335
336         /* the target must be in the requested direction */
337         if (distance <= 0)
338             continue;
339
340         /* Calculate score for this window.  The smaller the better. */
341         score = distance + offset;
342
343         /* windows more than 45 degrees off the direction are
344          * heavily penalized and will only be chosen if nothing
345          * else within a million pixels */
346         if (offset > distance)
347             score += 1000000;
348
349         if (best_score == -1 || score < best_score) {
350             best_client = cur;
351             best_score = score;
352         }
353     }
354
355     return best_client;
356 }
357
358 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
359                              gboolean desktop_windows, gboolean interactive,
360                              gboolean dialog, gboolean done, gboolean cancel)
361 {
362     static ObClient *first = NULL;
363     ObClient *ft = NULL;
364
365     if (!interactive)
366         return;
367
368     if (cancel) {
369         focus_cycle_target = NULL;
370         goto done_cycle;
371     } else if (done)
372         goto done_cycle;
373
374     if (!focus_order)
375         goto done_cycle;
376
377     if (focus_cycle_target == NULL) {
378         focus_cycle_iconic_windows = FALSE;
379         focus_cycle_all_desktops = FALSE;
380         focus_cycle_dock_windows = dock_windows;
381         focus_cycle_desktop_windows = desktop_windows;
382     }
383
384     if (!first) first = focus_client;
385
386     if (focus_cycle_target)
387         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
388                                     desktop_windows);
389     else if (first)
390         ft = focus_find_directional(first, dir, dock_windows, desktop_windows);
391     else {
392         GList *it;
393
394         for (it = focus_order; it; it = g_list_next(it))
395             if (focus_cycle_target_valid(it->data,
396                                          focus_cycle_iconic_windows,
397                                          focus_cycle_all_desktops,
398                                          focus_cycle_dock_windows,
399                                          focus_cycle_desktop_windows))
400                 ft = it->data;
401     }
402         
403     if (ft) {
404         if (ft != focus_cycle_target) {/* prevents flicker */
405             focus_cycle_target = ft;
406             focus_cycle_draw_indicator(ft);
407         }
408     }
409     if (focus_cycle_target && dialog) {
410         /* same arguments as focus_target_valid */
411         focus_cycle_popup_single_show(focus_cycle_target,
412                                       focus_cycle_iconic_windows,
413                                       focus_cycle_all_desktops,
414                                       focus_cycle_dock_windows,
415                                       focus_cycle_desktop_windows);
416         return;
417     }
418
419 done_cycle:
420     if (done && focus_cycle_target)
421         client_activate(focus_cycle_target, FALSE, TRUE);
422
423     first = NULL;
424     focus_cycle_target = NULL;
425
426     focus_cycle_draw_indicator(NULL);
427     focus_cycle_popup_single_hide();
428
429     return;
430 }