can drag slit-apps around in the slit
[dana/openbox.git] / openbox / slit.c
1 #include "slit.h"
2 #include "screen.h"
3 #include "grab.h"
4 #include "timer.h"
5 #include "openbox.h"
6 #include "render/theme.h"
7 #include "render/render.h"
8
9 #define SLIT_EVENT_MASK (EnterWindowMask | LeaveWindowMask)
10 #define SLITAPP_EVENT_MASK (StructureNotifyMask)
11
12 struct Slit {
13     Window frame;
14
15     /* user-requested position stuff */
16     SlitPosition pos;
17     int gravity;
18     int user_x, user_y;
19
20     /* actual position (when not auto-hidden) */
21     int x, y;
22     int w, h;
23
24     gboolean horz;
25     gboolean hide;
26     gboolean hidden;
27
28     Appearance *a_frame;
29
30     Timer *hide_timer;
31
32     GList *slit_apps;
33 };
34
35 GHashTable *slit_map = NULL;
36 GHashTable *slit_app_map = NULL;
37
38 static Slit *slit;
39 static int nslits;
40
41 static guint slit_hide_timeout = 3000; /* XXX make a config option */
42
43 static void slit_configure(Slit *self);
44
45 void slit_startup()
46 {
47     XSetWindowAttributes attrib;
48     int i;
49
50     slit_map = g_hash_table_new(g_int_hash, g_int_equal);
51     slit_app_map = g_hash_table_new(g_int_hash, g_int_equal);
52
53     nslits = 1;
54     slit = g_new0(struct Slit, nslits);
55
56     for (i = 0; i < nslits; ++i) {
57         slit[i].horz = FALSE;
58         slit[i].hide = FALSE;
59         slit[i].hidden = TRUE;
60         slit[i].pos = SlitPos_TopRight;
61
62         attrib.event_mask = SLIT_EVENT_MASK;
63         attrib.override_redirect = True;
64         slit[i].frame = XCreateWindow(ob_display, ob_root, 0, 0, 1, 1, 0,
65                                       render_depth, InputOutput, render_visual,
66                                       CWOverrideRedirect | CWEventMask,
67                                       &attrib);
68         slit[i].a_frame = appearance_copy(theme_a_unfocused_title);
69         XSetWindowBorder(ob_display, slit[i].frame, theme_b_color->pixel);
70         XSetWindowBorderWidth(ob_display, slit[i].frame, theme_bwidth);
71
72         g_hash_table_insert(slit_map, &slit[i].frame, &slit[i]);
73     }
74 }
75
76 void slit_shutdown()
77 {
78     int i;
79
80     for (i = 0; i < nslits; ++i) {
81         XDestroyWindow(ob_display, slit[i].frame);
82         appearance_free(slit[i].a_frame);
83         g_hash_table_remove(slit_map, &slit[i].frame);
84     }
85     g_hash_table_destroy(slit_app_map);
86     g_hash_table_destroy(slit_map);
87 }
88
89 void slit_add(Window win, XWMHints *wmhints, XWindowAttributes *attrib)
90 {
91     Slit *s;
92     SlitApp *app;
93
94     /* XXX pick a slit */
95     s = &slit[0];
96
97     app = g_new0(SlitApp, 1);
98     app->slit = s;
99     app->win = win;
100     app->icon_win = (wmhints->flags & IconWindowHint) ?
101         wmhints->icon_window : win;
102     
103     app->w = attrib->width;
104     app->h = attrib->height;
105
106     s->slit_apps = g_list_append(s->slit_apps, app);
107     slit_configure(s);
108
109     XReparentWindow(ob_display, app->icon_win, s->frame, app->x, app->y);
110     /*
111       This is the same case as in frame.c for client windows. When Openbox is
112       starting, the window is already mapped so we see unmap events occur for
113       it. There are 2 unmap events generated that we see, one with the 'event'
114       member set the root window, and one set to the client, but both get
115       handled and need to be ignored.
116     */
117     if (ob_state == State_Starting)
118         app->ignore_unmaps += 2;
119
120     if (app->win != app->icon_win) {
121         /* have to map it so that it can be re-managed on a restart */
122         XMoveWindow(ob_display, app->win, -1000, -1000);
123         XMapWindow(ob_display, app->win);
124     }
125     XMapWindow(ob_display, app->icon_win);
126     XSync(ob_display, False);
127
128     /* specify that if we exit, the window should not be destroyed and should
129        be reparented back to root automatically */
130     XChangeSaveSet(ob_display, app->icon_win, SetModeInsert);
131     XSelectInput(ob_display, app->icon_win, SLITAPP_EVENT_MASK);
132
133     grab_button_full(2, 0, app->icon_win, ButtonMotionMask, GrabModeAsync,
134                      ob_cursors.move);
135
136     g_hash_table_insert(slit_app_map, &app->icon_win, app);
137
138     g_message("Managed Slit App: 0x%lx", app->icon_win);
139 }
140
141 void slit_remove_all()
142 {
143     int i;
144
145     for (i = 0; i < nslits; ++i)
146         while (slit[i].slit_apps)
147             slit_remove(slit[i].slit_apps->data, TRUE);
148 }
149
150 void slit_remove(SlitApp *app, gboolean reparent)
151 {
152     ungrab_button(2, 0, app->icon_win);
153     XSelectInput(ob_display, app->icon_win, NoEventMask);
154     /* remove the window from our save set */
155     XChangeSaveSet(ob_display, app->icon_win, SetModeDelete);
156     XSync(ob_display, False);
157
158     g_hash_table_remove(slit_app_map, &app->icon_win);
159
160     if (reparent)
161         XReparentWindow(ob_display, app->icon_win, ob_root, app->x, app->y);
162
163     app->slit->slit_apps = g_list_remove(app->slit->slit_apps, app);
164     slit_configure(app->slit);
165
166     g_message("Unmanaged Slit App: 0x%lx", app->icon_win);
167
168     g_free(app);
169 }
170
171 void slit_configure_all()
172 {
173     int i; for (i = 0; i < nslits; ++i) slit_configure(&slit[i]);
174 }
175
176 static void slit_configure(Slit *self)
177 {
178     GList *it;
179     int spot;
180
181     self->w = self->h = spot = 0;
182
183     for (it = self->slit_apps; it; it = it->next) {
184         struct SlitApp *app = it->data;
185         if (self->horz) {
186             app->x = spot;
187             app->y = 0;
188             self->w += app->w;
189             self->h = MAX(self->h, app->h);
190             spot += app->w;
191         } else {
192             app->x = 0;
193             app->y = spot;
194             self->w = MAX(self->w, app->w);
195             self->h += app->h;
196             spot += app->h;
197         }
198
199         XMoveWindow(ob_display, app->icon_win, app->x, app->y);
200     }
201
202     /* used for calculating offsets */
203     self->w += theme_bwidth * 2;
204     self->h += theme_bwidth * 2;
205
206     /* calculate position */
207     switch (self->pos) {
208     case SlitPos_Floating:
209         self->x = self->user_x;
210         self->y = self->user_y;
211         break;
212     case SlitPos_TopLeft:
213         self->x = 0;
214         self->y = 0;
215         self->gravity = NorthWestGravity;
216         break;
217     case SlitPos_Top:
218         self->x = screen_physical_size.width / 2;
219         self->y = 0;
220         self->gravity = NorthGravity;
221         break;
222     case SlitPos_TopRight:
223         self->x = screen_physical_size.width;
224         self->y = 0;
225         self->gravity = NorthEastGravity;
226         break;
227     case SlitPos_Left:
228         self->x = 0;
229         self->y = screen_physical_size.height / 2;
230         self->gravity = WestGravity;
231         break;
232     case SlitPos_Right:
233         self->x = screen_physical_size.width;
234         self->y = screen_physical_size.height / 2;
235         self->gravity = EastGravity;
236         break;
237     case SlitPos_BottomLeft:
238         self->x = 0;
239         self->y = screen_physical_size.height;
240         self->gravity = SouthWestGravity;
241         break;
242     case SlitPos_Bottom:
243         self->x = screen_physical_size.width / 2;
244         self->y = screen_physical_size.height;
245         self->gravity = SouthGravity;
246         break;
247     case SlitPos_BottomRight:
248         self->x = screen_physical_size.width;
249         self->y = screen_physical_size.height;
250         self->gravity = SouthEastGravity;
251         break;
252     }
253
254     switch(self->gravity) {
255     case NorthGravity:
256     case CenterGravity:
257     case SouthGravity:
258         self->x -= self->w / 2;
259         break;
260     case NorthEastGravity:
261     case EastGravity:
262     case SouthEastGravity:
263         self->x -= self->w;
264         break;
265     }
266     switch(self->gravity) {
267     case WestGravity:
268     case CenterGravity:
269     case EastGravity:
270         self->y -= self->h / 2;
271         break;
272     case SouthWestGravity:
273     case SouthGravity:
274     case SouthEastGravity:
275         self->y -= self->h;
276         break;
277     }
278
279     if (self->hide && self->hidden) {
280         g_message("hidden");
281         switch (self->pos) {
282         case SlitPos_Floating:
283             break;
284         case SlitPos_TopLeft:
285             if (self->horz)
286                 self->y -= self->h - theme_bwidth;
287             else
288                 self->x -= self->w - theme_bwidth;
289             break;
290         case SlitPos_Top:
291             self->y -= self->h - theme_bwidth;
292             break;
293         case SlitPos_TopRight:
294             if (self->horz)
295                 self->y -= self->h - theme_bwidth;
296             else
297                 self->x += self->w - theme_bwidth;
298             break;
299         case SlitPos_Left:
300             self->x -= self->w - theme_bwidth;
301             break;
302         case SlitPos_Right:
303             self->x += self->w - theme_bwidth;
304             break;
305         case SlitPos_BottomLeft:
306             if (self->horz)
307                 self->y += self->h - theme_bwidth;
308             else
309                 self->x -= self->w - theme_bwidth;
310             break;
311         case SlitPos_Bottom:
312             self->y += self->h - theme_bwidth;
313             break;
314         case SlitPos_BottomRight:
315             if (self->horz)
316                 self->y += self->h - theme_bwidth;
317             else
318                 self->x += self->w - theme_bwidth;
319             break;
320         }    
321     }
322
323     /* not used for actually sizing shit */
324     self->w -= theme_bwidth * 2;
325     self->h -= theme_bwidth * 2;
326
327     if (self->w > 0 && self->h > 0) {
328         RECT_SET(self->a_frame->area, 0, 0, self->w, self->h);
329         XMoveResizeWindow(ob_display, self->frame,
330                           self->x, self->y, self->w, self->h);
331
332         paint(self->frame, self->a_frame);
333         XMapWindow(ob_display, self->frame);
334     } else
335         XUnmapWindow(ob_display, self->frame);
336
337     /* but they are useful outside of this function! */
338     self->w += theme_bwidth * 2;
339     self->h += theme_bwidth * 2;
340 }
341
342 void slit_app_configure(SlitApp *app, int w, int h)
343 {
344     app->w = w;
345     app->h = h;
346     slit_configure(app->slit);
347 }
348
349 void slit_app_drag(SlitApp *app, XMotionEvent *e)
350 {
351     Slit *src, *dest = NULL;
352     SlitApp *over = NULL;
353     GList *it;
354     int i;
355     int x, y;
356     gboolean after;
357
358     src = app->slit;
359     x = e->x_root;
360     y = e->y_root;
361
362     /* which slit are we on top of? */
363     for (i = 0; i < nslits; ++i)
364         if (x >= slit[i].x &&
365             y >= slit[i].y &&
366             x < slit[i].x + slit[i].w &&
367             y < slit[i].y + slit[i].h) {
368             dest = &slit[i];
369             break;
370         }
371     if (!dest) return;
372
373     x -= dest->x;
374     y -= dest->y;
375
376     /* which slit app are we on top of? */
377     for (it = dest->slit_apps; it; it = it->next) {
378         over = it->data;
379         if (dest->horz) {
380             if (x >= over->x && x < over->x + over->w)
381                 break;
382         } else {
383             if (y >= over->y && y < over->y + over->h)
384                 break;
385         }
386     }
387     if (!it || app == over) return;
388
389     x -= over->x;
390     y -= over->y;
391
392     if (dest->horz)
393         after = (x > over->w / 2);
394     else
395         after = (y > over->h / 2);
396
397     /* remove before doing the it->next! */
398     src->slit_apps = g_list_remove(src->slit_apps, app);
399     if (src != dest) slit_configure(src);
400
401     if (after) it = it->next;
402
403     dest->slit_apps = g_list_insert_before(dest->slit_apps, it, app);
404     slit_configure(dest);
405 }
406
407 static void hide_timeout(Slit *self)
408 {
409     /* dont repeat */
410     timer_stop(self->hide_timer);
411     self->hide_timer = NULL;
412
413     /* hide */
414     self->hidden = TRUE;
415     slit_configure(self);
416 }
417
418 void slit_hide(Slit *self, gboolean hide)
419 {
420     if (self->hidden == hide || !self->hide)
421         return;
422     if (!hide) {
423         /* show */
424         self->hidden = FALSE;
425         slit_configure(self);
426
427         /* if was hiding, stop it */
428         if (self->hide_timer) {
429             timer_stop(self->hide_timer);
430             self->hide_timer = NULL;
431         }
432     } else {
433         g_assert(!self->hide_timer);
434         self->hide_timer = timer_start(slit_hide_timeout * 1000,
435                                        (TimeoutHandler)hide_timeout, self);
436     }
437 }