add a comment and make it smarter about when to let you focus cycle to windows with...
[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 *first = NULL;
173     static ObClient *t = NULL;
174     static GList *order = NULL;
175     GList *it, *start, *list;
176     ObClient *ft = NULL;
177
178     if (interactive) {
179         if (cancel) {
180             focus_cycle_target = NULL;
181             goto done_cycle;
182         } else if (done)
183             goto done_cycle;
184
185         if (!focus_order)
186             goto done_cycle;
187
188         if (!first) first = focus_client;
189
190         if (linear) list = client_list;
191         else        list = focus_order;
192     } else {
193         if (!focus_order)
194             goto done_cycle;
195         list = client_list;
196     }
197
198
199     if (focus_cycle_target == NULL) {
200         focus_cycle_iconic_windows = TRUE;
201         focus_cycle_all_desktops = all_desktops;
202         focus_cycle_dock_windows = dock_windows;
203         focus_cycle_desktop_windows = desktop_windows;
204         focus_cycle_target = focus_client;
205     }
206
207     start = it = g_list_find(list, focus_cycle_target);
208     if (!start) /* switched desktops or something? */
209         start = it = forward ? g_list_last(list) : g_list_first(list);
210     if (!start) goto done_cycle;
211
212     do {
213         if (forward) {
214             it = it->next;
215             if (it == NULL) it = g_list_first(list);
216         } else {
217             it = it->prev;
218             if (it == NULL) it = g_list_last(list);
219         }
220         ft = it->data;
221         if (focus_cycle_target_valid(ft,
222                                      focus_cycle_iconic_windows,
223                                      focus_cycle_all_desktops,
224                                      focus_cycle_dock_windows,
225                                      focus_cycle_desktop_windows))
226         {
227             if (interactive) {
228                 if (ft != focus_cycle_target) { /* prevents flicker */
229                     focus_cycle_target = ft;
230                     focus_cycle_draw_indicator(ft);
231                 }
232                 if (dialog)
233                     /* same arguments as focus_target_valid */
234                     focus_cycle_popup_show(ft,
235                                            focus_cycle_iconic_windows,
236                                            focus_cycle_all_desktops,
237                                            focus_cycle_dock_windows,
238                                            focus_cycle_desktop_windows);
239                 return;
240             } else if (ft != focus_cycle_target) {
241                 focus_cycle_target = ft;
242                 done = TRUE;
243                 break;
244             }
245         }
246     } while (it != start);
247
248 done_cycle:
249     if (done && focus_cycle_target)
250         client_activate(focus_cycle_target, FALSE, TRUE);
251
252     t = NULL;
253     first = NULL;
254     focus_cycle_target = NULL;
255     g_list_free(order);
256     order = NULL;
257
258     if (interactive) {
259         focus_cycle_draw_indicator(NULL);
260         focus_cycle_popup_hide();
261     }
262
263     return;
264 }
265
266 /* this be mostly ripped from fvwm */
267 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
268                                         gboolean dock_windows,
269                                         gboolean desktop_windows) 
270 {
271     gint my_cx, my_cy, his_cx, his_cy;
272     gint offset = 0;
273     gint distance = 0;
274     gint score, best_score;
275     ObClient *best_client, *cur;
276     GList *it;
277
278     if(!client_list)
279         return NULL;
280
281     /* first, find the centre coords of the currently focused window */
282     my_cx = c->frame->area.x + c->frame->area.width / 2;
283     my_cy = c->frame->area.y + c->frame->area.height / 2;
284
285     best_score = -1;
286     best_client = NULL;
287
288     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
289         cur = it->data;
290
291         /* the currently selected window isn't interesting */
292         if(cur == c)
293             continue;
294         if (!focus_cycle_target_valid(it->data, FALSE, FALSE, dock_windows,
295                                       desktop_windows))
296             continue;
297
298         /* find the centre coords of this window, from the
299          * currently focused window's point of view */
300         his_cx = (cur->frame->area.x - my_cx)
301             + cur->frame->area.width / 2;
302         his_cy = (cur->frame->area.y - my_cy)
303             + cur->frame->area.height / 2;
304
305         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
306            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
307             gint tx;
308             /* Rotate the diagonals 45 degrees counterclockwise.
309              * To do this, multiply the matrix /+h +h\ with the
310              * vector (x y).                   \-h +h/
311              * h = sqrt(0.5). We can set h := 1 since absolute
312              * distance doesn't matter here. */
313             tx = his_cx + his_cy;
314             his_cy = -his_cx + his_cy;
315             his_cx = tx;
316         }
317
318         switch(dir) {
319         case OB_DIRECTION_NORTH:
320         case OB_DIRECTION_SOUTH:
321         case OB_DIRECTION_NORTHEAST:
322         case OB_DIRECTION_SOUTHWEST:
323             offset = (his_cx < 0) ? -his_cx : his_cx;
324             distance = ((dir == OB_DIRECTION_NORTH ||
325                          dir == OB_DIRECTION_NORTHEAST) ?
326                         -his_cy : his_cy);
327             break;
328         case OB_DIRECTION_EAST:
329         case OB_DIRECTION_WEST:
330         case OB_DIRECTION_SOUTHEAST:
331         case OB_DIRECTION_NORTHWEST:
332             offset = (his_cy < 0) ? -his_cy : his_cy;
333             distance = ((dir == OB_DIRECTION_WEST ||
334                          dir == OB_DIRECTION_NORTHWEST) ?
335                         -his_cx : his_cx);
336             break;
337         }
338
339         /* the target must be in the requested direction */
340         if(distance <= 0)
341             continue;
342
343         /* Calculate score for this window.  The smaller the better. */
344         score = distance + offset;
345
346         /* windows more than 45 degrees off the direction are
347          * heavily penalized and will only be chosen if nothing
348          * else within a million pixels */
349         if(offset > distance)
350             score += 1000000;
351
352         if(best_score == -1 || score < best_score)
353             best_client = cur,
354                 best_score = score;
355     }
356
357     return best_client;
358 }
359
360 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
361                              gboolean desktop_windows, gboolean interactive,
362                              gboolean dialog, gboolean done, gboolean cancel)
363 {
364     static ObClient *first = NULL;
365     ObClient *ft = NULL;
366
367     if (!interactive)
368         return;
369
370     if (cancel) {
371         focus_cycle_target = NULL;
372         goto done_cycle;
373     } else if (done)
374         goto done_cycle;
375
376     if (!focus_order)
377         goto done_cycle;
378
379     if (focus_cycle_target == NULL) {
380         focus_cycle_iconic_windows = FALSE;
381         focus_cycle_all_desktops = FALSE;
382         focus_cycle_dock_windows = dock_windows;
383         focus_cycle_desktop_windows = desktop_windows;
384         focus_cycle_target = focus_client;
385     }
386
387     if (!first) first = focus_client;
388
389     if (focus_cycle_target)
390         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
391                                     desktop_windows);
392     else {
393         GList *it;
394
395         for (it = focus_order; it; it = g_list_next(it))
396             if (focus_cycle_target_valid(it->data,
397                                          focus_cycle_iconic_windows,
398                                          focus_cycle_all_desktops,
399                                          focus_cycle_dock_windows,
400                                          focus_cycle_desktop_windows))
401                 ft = it->data;
402     }
403         
404     if (ft) {
405         if (ft != focus_cycle_target) {/* prevents flicker */
406             focus_cycle_target = ft;
407             focus_cycle_draw_indicator(ft);
408         }
409     }
410     if (focus_cycle_target && dialog) {
411         /* same arguments as focus_target_valid */
412         focus_cycle_popup_single_show(focus_cycle_target,
413                                       focus_cycle_iconic_windows,
414                                       focus_cycle_all_desktops,
415                                       focus_cycle_dock_windows,
416                                       focus_cycle_desktop_windows);
417         return;
418     }
419
420 done_cycle:
421     if (done && focus_cycle_target)
422         client_activate(focus_cycle_target, FALSE, TRUE);
423
424     first = NULL;
425     focus_cycle_target = NULL;
426
427     focus_cycle_draw_indicator(NULL);
428     focus_cycle_popup_single_hide();
429
430     return;
431 }