make next/previouswindow and dirfocus actions show the focus_cycle_indicator even...
[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     return best_client;
355 }
356
357 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
358                              gboolean desktop_windows, gboolean interactive,
359                              gboolean dialog, gboolean done, gboolean cancel)
360 {
361     static ObClient *first = NULL;
362     ObClient *ft = NULL;
363
364     if (!interactive)
365         return;
366
367     if (cancel) {
368         focus_cycle_target = NULL;
369         goto done_cycle;
370     } else if (done)
371         goto done_cycle;
372
373     if (!focus_order)
374         goto done_cycle;
375
376     if (focus_cycle_target == NULL) {
377         focus_cycle_iconic_windows = FALSE;
378         focus_cycle_all_desktops = FALSE;
379         focus_cycle_dock_windows = dock_windows;
380         focus_cycle_desktop_windows = desktop_windows;
381     }
382
383     if (!first) first = focus_client;
384
385     if (focus_cycle_target)
386         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
387                                     desktop_windows);
388     else if (first)
389         ft = focus_find_directional(first, dir, dock_windows, desktop_windows);
390     else {
391         GList *it;
392
393         for (it = focus_order; it; it = g_list_next(it))
394             if (focus_cycle_target_valid(it->data,
395                                          focus_cycle_iconic_windows,
396                                          focus_cycle_all_desktops,
397                                          focus_cycle_dock_windows,
398                                          focus_cycle_desktop_windows))
399                 ft = it->data;
400     }
401         
402     if (ft) {
403         if (ft != focus_cycle_target) {/* prevents flicker */
404             focus_cycle_target = ft;
405             focus_cycle_draw_indicator(ft);
406         }
407     }
408     if (focus_cycle_target && dialog) {
409         /* same arguments as focus_target_valid */
410         focus_cycle_popup_single_show(focus_cycle_target,
411                                       focus_cycle_iconic_windows,
412                                       focus_cycle_all_desktops,
413                                       focus_cycle_dock_windows,
414                                       focus_cycle_desktop_windows);
415         return;
416     }
417
418 done_cycle:
419     if (done && focus_cycle_target)
420         client_activate(focus_cycle_target, FALSE, TRUE);
421
422     first = NULL;
423     focus_cycle_target = NULL;
424
425     focus_cycle_draw_indicator(NULL);
426     focus_cycle_popup_single_hide();
427
428     return;
429 }