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