awful.util: add table.keys
[awesome.git] / widget.c
blobc2f64303b9f568ff39b574ce2e66208e9f71d39c
1 /*
2 * widget.c - widget managing
4 * Copyright © 2007-2009 Julien Danjou <julien@danjou.info>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include <math.h>
24 #include <xcb/xcb.h>
25 #include <xcb/xcb_atom.h>
27 #include "screen.h"
28 #include "mouse.h"
29 #include "widget.h"
30 #include "wibox.h"
31 #include "client.h"
32 #include "common/atoms.h"
33 #include "common/xutil.h"
35 DO_LUA_TOSTRING(widget_t, widget, "widget")
37 /** Collect a widget structure.
38 * \param L The Lua VM state.
39 * \return 0
41 static int
42 luaA_widget_gc(lua_State *L)
44 widget_t *widget = luaL_checkudata(L, 1, "widget");
45 if(widget->destructor)
46 widget->destructor(widget);
47 button_array_wipe(&widget->buttons);
48 luaL_unref(globalconf.L, LUA_REGISTRYINDEX, widget->mouse_enter);
49 luaL_unref(globalconf.L, LUA_REGISTRYINDEX, widget->mouse_leave);
50 return luaA_object_gc(L);
53 /** Delete a widget node structure.
54 * \param node The node to destroy.
56 void
57 widget_node_delete(widget_node_t *node)
59 widget_unref(globalconf.L, node->widget);
62 /** Get a widget node from a wibox by coords.
63 * \param orientation Wibox orientation.
64 * \param widgets The widget list.
65 * \param width The container width.
66 * \param height The container height.
67 * \param x X coordinate of the widget.
68 * \param y Y coordinate of the widget.
69 * \return A widget.
71 widget_t *
72 widget_getbycoords(orientation_t orientation, widget_node_array_t *widgets,
73 int width, int height, int16_t *x, int16_t *y)
75 int tmp;
77 /* Need to transform coordinates like it was top/bottom */
78 switch(orientation)
80 case South:
81 tmp = *y;
82 *y = width - *x;
83 *x = tmp;
84 break;
85 case North:
86 tmp = *y;
87 *y = *x;
88 *x = height - tmp;
89 break;
90 default:
91 break;
93 foreach(w, *widgets)
94 if(w->widget->isvisible
95 && *x >= w->geometry.x && *x < w->geometry.x + w->geometry.width
96 && *y >= w->geometry.y && *y < w->geometry.y + w->geometry.height)
97 return w->widget;
99 return NULL;
102 /** Convert a Lua table to a list of widget nodet.
103 * \param L The Lua VM state.
104 * \param widgets The linked list of widget node.
106 static void
107 luaA_table2widgets(lua_State *L, widget_node_array_t *widgets)
109 if(lua_istable(L, -1))
111 lua_pushnil(L);
112 while(luaA_next(L, -2))
113 luaA_table2widgets(L, widgets);
114 /* remove the table */
115 lua_pop(L, 1);
117 else
119 widget_t *widget = luaA_toudata(L, -1, "widget");
120 if(widget)
122 widget_node_t w;
123 p_clear(&w, 1);
124 w.widget = widget_ref(L, -1);
125 widget_node_array_append(widgets, w);
127 else
128 lua_pop(L, 1); /* remove value */
132 /** Render a list of widgets.
133 * \param wibox The wibox.
134 * \todo Remove GC.
136 void
137 widget_render(wibox_t *wibox)
139 draw_context_t *ctx = &wibox->sw.ctx;
140 int left = 0, right = 0;
141 area_t rectangle = { 0, 0, 0, 0 };
142 color_t col;
144 rectangle.width = ctx->width;
145 rectangle.height = ctx->height;
147 if(ctx->bg.alpha != 0xffff)
149 int x = wibox->sw.geometry.x, y = wibox->sw.geometry.y;
150 xcb_get_property_reply_t *prop_r;
151 char *data;
152 xcb_pixmap_t rootpix;
153 xcb_get_property_cookie_t prop_c;
154 xcb_screen_t *s = xutil_screen_get(globalconf.connection, ctx->phys_screen);
155 prop_c = xcb_get_property_unchecked(globalconf.connection, false, s->root, _XROOTPMAP_ID,
156 PIXMAP, 0, 1);
157 if((prop_r = xcb_get_property_reply(globalconf.connection, prop_c, NULL)))
159 if(prop_r->value_len
160 && (data = xcb_get_property_value(prop_r))
161 && (rootpix = *(xcb_pixmap_t *) data))
162 switch(wibox->sw.orientation)
164 case North:
165 draw_rotate(ctx,
166 rootpix, ctx->pixmap,
167 s->width_in_pixels, s->height_in_pixels,
168 ctx->width, ctx->height,
169 M_PI_2,
170 y + ctx->width,
171 - x);
172 break;
173 case South:
174 draw_rotate(ctx,
175 rootpix, ctx->pixmap,
176 s->width_in_pixels, s->height_in_pixels,
177 ctx->width, ctx->height,
178 - M_PI_2,
179 - y,
180 x + ctx->height);
181 break;
182 case East:
183 xcb_copy_area(globalconf.connection, rootpix,
184 wibox->sw.pixmap, wibox->sw.gc,
185 x, y,
186 0, 0,
187 ctx->width, ctx->height);
188 break;
190 p_delete(&prop_r);
194 widget_node_array_t *widgets = &wibox->widgets;
196 widget_node_array_wipe(widgets);
197 widget_node_array_init(widgets);
198 lua_rawgeti(globalconf.L, LUA_REGISTRYINDEX, wibox->widgets_table);
199 luaA_table2widgets(globalconf.L, widgets);
201 /* compute geometry */
202 for(int i = 0; i < widgets->len; i++)
203 if(widgets->tab[i].widget->align == AlignLeft
204 && widgets->tab[i].widget->isvisible)
206 widgets->tab[i].geometry = widgets->tab[i].widget->geometry(widgets->tab[i].widget,
207 wibox->screen, ctx->height,
208 ctx->width - (left + right));
209 widgets->tab[i].geometry.x = left;
210 left += widgets->tab[i].geometry.width;
213 for(int i = widgets->len - 1; i >= 0; i--)
214 if(widgets->tab[i].widget->align == AlignRight && widgets->tab[i].widget->isvisible)
216 widgets->tab[i].geometry = widgets->tab[i].widget->geometry(widgets->tab[i].widget,
217 wibox->screen, ctx->height,
218 ctx->width - (left + right));
219 right += widgets->tab[i].geometry.width;
220 widgets->tab[i].geometry.x = ctx->width - right;
223 /* save left value */
224 int fake_left = left;
226 /* compute width of flex or fixed aligned widgets */
227 int flex = 0;
228 for(int i = 0; i < widgets->len; i++)
229 if(widgets->tab[i].widget->align & (AlignFlex | AlignFixed)
230 && widgets->tab[i].widget->isvisible)
232 if(widgets->tab[i].widget->align_supported & AlignFlex
233 && widgets->tab[i].widget->align == AlignFlex)
234 flex++;
235 else
236 fake_left += widgets->tab[i].widget->geometry(widgets->tab[i].widget,
237 wibox->screen, ctx->height,
238 ctx->width - (fake_left + right)).width;
241 /* now compute everybody together! */
242 int flex_rendered = 0;
243 for(int i = 0; i < widgets->len; i++)
244 if(widgets->tab[i].widget->align & (AlignFlex | AlignFixed)
245 && widgets->tab[i].widget->isvisible)
247 if(widgets->tab[i].widget->align_supported & AlignFlex
248 && widgets->tab[i].widget->align == AlignFlex)
250 int width = (ctx->width - (right + fake_left)) / flex;
251 /* give last pixels to last flex to be rendered */
252 if(flex_rendered == flex - 1)
253 width += (ctx->width - (right + fake_left)) % flex;
254 widgets->tab[i].geometry = widgets->tab[i].widget->geometry(widgets->tab[i].widget,
255 wibox->screen, ctx->height,
256 width);
257 flex_rendered++;
259 else
260 widgets->tab[i].geometry = widgets->tab[i].widget->geometry(widgets->tab[i].widget,
261 wibox->screen, ctx->height,
262 ctx->width - (left + right));
263 widgets->tab[i].geometry.x = left;
264 left += widgets->tab[i].geometry.width;
267 /* draw background image, only if the background color is not opaque */
268 if(wibox->bg_image && ctx->bg.alpha != 0xffff)
269 draw_image(ctx, 0, 0, 1.0, wibox->bg_image);
271 /* draw background color */
272 xcolor_to_color(&ctx->bg, &col);
273 draw_rectangle(ctx, rectangle, 1.0, true, &col);
275 /* draw everything! */
276 for(int i = 0; i < widgets->len; i++)
277 if(widgets->tab[i].widget->isvisible)
279 widgets->tab[i].geometry.y = 0;
280 widgets->tab[i].widget->draw(widgets->tab[i].widget,
281 ctx, widgets->tab[i].geometry, wibox);
284 switch(wibox->sw.orientation)
286 case South:
287 draw_rotate(ctx, ctx->pixmap, wibox->sw.pixmap,
288 ctx->width, ctx->height,
289 ctx->height, ctx->width,
290 M_PI_2, ctx->height, 0);
291 break;
292 case North:
293 draw_rotate(ctx, ctx->pixmap, wibox->sw.pixmap,
294 ctx->width, ctx->height,
295 ctx->height, ctx->width,
296 - M_PI_2, 0, ctx->width);
297 break;
298 case East:
299 break;
303 /** Invalidate widgets which should be refresh depending on their types.
304 * \param type Widget type to invalidate.
306 void
307 widget_invalidate_bytype(widget_constructor_t *type)
309 foreach(wibox, globalconf.wiboxes)
310 foreach(wnode, (*wibox)->widgets)
311 if(wnode->widget->type == type)
313 (*wibox)->need_update = true;
314 break;
318 /** Set a wibox needs update because it has widget, or redraw a titlebar.
319 * \param widget The widget to look for.
321 void
322 widget_invalidate_bywidget(widget_t *widget)
324 foreach(wibox, globalconf.wiboxes)
325 if(!(*wibox)->need_update)
326 foreach(wnode, (*wibox)->widgets)
327 if(wnode->widget == widget)
329 (*wibox)->need_update = true;
330 break;
333 foreach(_c, globalconf.clients)
335 client_t *c = *_c;
336 if(c->titlebar && !c->titlebar->need_update)
337 for(int j = 0; j < c->titlebar->widgets.len; j++)
338 if(c->titlebar->widgets.tab[j].widget == widget)
340 c->titlebar->need_update = true;
341 break;
346 /** Create a new widget.
347 * \param L The Lua VM state.
349 * \luastack
350 * \lparam A table with at least a type value. Optional attributes
351 * are: align.
352 * \lreturn A brand new widget.
354 static int
355 luaA_widget_new(lua_State *L)
357 const char *align, *type;
358 widget_t *w;
359 widget_constructor_t *wc = NULL;
360 size_t len;
362 luaA_checktable(L, 2);
364 type = luaA_getopt_lstring(L, 2, "type", NULL, &len);
366 switch(a_tokenize(type, len))
368 case A_TK_TEXTBOX:
369 wc = widget_textbox;
370 break;
371 case A_TK_PROGRESSBAR:
372 wc = widget_progressbar;
373 break;
374 case A_TK_GRAPH:
375 wc = widget_graph;
376 break;
377 case A_TK_SYSTRAY:
378 wc = widget_systray;
379 break;
380 case A_TK_IMAGEBOX:
381 wc = widget_imagebox;
382 break;
383 default:
384 break;
387 if(wc)
389 w = widget_new(L);
390 wc(w);
392 else
394 luaA_warn(L, "unkown widget type: %s", type);
395 return 0;
398 w->type = wc;
400 align = luaA_getopt_lstring(L, 2, "align", "left", &len);
401 w->align_supported |= AlignLeft | AlignRight | AlignFixed;
402 w->align = draw_align_fromstr(align, len);
404 /* Set visible by default. */
405 w->isvisible = true;
407 w->mouse_enter = w->mouse_leave = LUA_REFNIL;
409 return 1;
412 /** Get or set mouse buttons bindings to a widget.
413 * \param L The Lua VM state.
415 * \luastack
416 * \lvalue A widget.
417 * \lparam An array of mouse button bindings objects, or nothing.
418 * \return The array of mouse button bindings objects of this widget.
420 static int
421 luaA_widget_buttons(lua_State *L)
423 widget_t *widget = luaL_checkudata(L, 1, "widget");
424 button_array_t *buttons = &widget->buttons;
426 if(lua_gettop(L) == 2)
428 luaA_button_array_set(L, 2, buttons);
429 return 1;
432 return luaA_button_array_get(L, buttons);
435 /** Generic widget.
436 * \param L The Lua VM state.
437 * \return The number of elements pushed on stack.
438 * \luastack
439 * \lfield align The widget alignment.
440 * \lfield visible The widget visibility.
441 * \lfield mouse_enter A function to execute when the mouse enter the widget.
442 * \lfield mouse_leave A function to execute when the mouse leave the widget.
444 static int
445 luaA_widget_index(lua_State *L)
447 size_t len;
448 widget_t *widget = luaL_checkudata(L, 1, "widget");
449 const char *buf = luaL_checklstring(L, 2, &len);
450 awesome_token_t token;
452 if(luaA_usemetatable(L, 1, 2))
453 return 1;
455 switch((token = a_tokenize(buf, len)))
457 case A_TK_ALIGN:
458 lua_pushstring(L, draw_align_tostr(widget->align));
459 return 1;
460 case A_TK_VISIBLE:
461 lua_pushboolean(L, widget->isvisible);
462 return 1;
463 case A_TK_MOUSE_ENTER:
464 if(widget->mouse_enter != LUA_REFNIL)
465 lua_rawgeti(L, LUA_REGISTRYINDEX, widget->mouse_enter);
466 else
467 return 0;
468 return 1;
469 case A_TK_MOUSE_LEAVE:
470 if(widget->mouse_leave != LUA_REFNIL)
471 lua_rawgeti(L, LUA_REGISTRYINDEX, widget->mouse_leave);
472 else
473 return 0;
474 return 1;
475 default:
476 break;
479 return widget->index ? widget->index(L, token) : 0;
482 /** Generic widget newindex.
483 * \param L The Lua VM state.
484 * \return The number of elements pushed on stack.
486 static int
487 luaA_widget_newindex(lua_State *L)
489 size_t len;
490 widget_t *widget = luaL_checkudata(L, 1, "widget");
491 const char *buf = luaL_checklstring(L, 2, &len);
492 awesome_token_t token;
494 switch((token = a_tokenize(buf, len)))
496 case A_TK_ALIGN:
497 buf = luaL_checklstring(L, 3, &len);
498 widget->align = draw_align_fromstr(buf, len);
499 break;
500 case A_TK_VISIBLE:
501 widget->isvisible = luaA_checkboolean(L, 3);
502 break;
503 case A_TK_MOUSE_ENTER:
504 luaA_registerfct(L, 3, &widget->mouse_enter);
505 return 0;
506 case A_TK_MOUSE_LEAVE:
507 luaA_registerfct(L, 3, &widget->mouse_leave);
508 return 0;
509 default:
510 return widget->newindex ? widget->newindex(L, token) : 0;
513 widget_invalidate_bywidget(widget);
515 return 0;
518 static int
519 luaA_widget_extents(lua_State *L)
521 widget_t *widget = luaL_checkudata(L, 1, "widget");
522 area_t g = {
523 .x = 0,
524 .y = 0,
525 .width = 0,
526 .height = 0
529 if(widget->extents)
530 g = widget->extents(L, widget);
532 lua_newtable(L);
533 lua_pushnumber(L, g.width);
534 lua_setfield(L, -2, "width");
535 lua_pushnumber(L, g.height);
536 lua_setfield(L, -2, "height");
538 return 1;
541 const struct luaL_reg awesome_widget_methods[] =
543 { "__call", luaA_widget_new },
544 { NULL, NULL }
546 const struct luaL_reg awesome_widget_meta[] =
548 { "buttons", luaA_widget_buttons },
549 { "extents", luaA_widget_extents },
550 { "__index", luaA_widget_index },
551 { "__newindex", luaA_widget_newindex },
552 { "__gc", luaA_widget_gc },
553 { "__tostring", luaA_widget_tostring },
554 { NULL, NULL }
557 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80