#include #include #include #include #include #include #include "btk-window.h" #include "btk-log.h" void draw_overlay (btk_window_t *, btk_area_t); void update_cell_area (btk_window_t *, btk_cell_t *); void update_stretch_area (btk_window_t *); btk_window_t* btk_window_create(xcb_connection_t *x_con, xcb_screen_t *x_scr, xcb_visualtype_t *x_vis, unsigned int cw, unsigned int ch, int sc, int sr, unsigned int ew, unsigned int eh, unsigned int cells_n, void (*func_kill)(void)) { btk_window_t *w = (btk_window_t*)malloc(sizeof(btk_window_t)); w->cells = (btk_cell_t*)(malloc(sizeof(btk_cell_t) * cells_n)); w->cells_n = cells_n; w->func_kill = func_kill; /* states setup */ w->state = BTK_WINDOW_STATE_INITIAL; w->cell_focus = -1; w->cell_press = -1; w->focus_in = -1; /* geometry setup */ w->cs.w = cw; w->cs.h = ch; w->es.w = sc < 0 ? 0 : ew; w->es.h = sr < 0 ? 0 : eh; w->ms.w = cw * (btk_cp.w + btk_frame) + btk_frame; w->ms.h = ch * (btk_cp.h + btk_frame) + btk_frame; w->ps.w = w->ms.w + w->es.w; w->ps.h = w->ms.h + w->es.h; w->sc.x = sc; w->sc.y = sr; update_stretch_area(w); /* xcb setup */ uint32_t mask_vals[2] = { x_scr->black_pixel, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS }; w->x_con = x_con; w->x_win = xcb_generate_id(x_con); xcb_create_window(x_con, XCB_COPY_FROM_PARENT, w->x_win, x_scr->root, 0, 0, w->ps.w, w->ps.h, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, x_scr->root_visual, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, mask_vals); /* wm hints setup */ btk_wm_size_hint_t x_hints = { .flags = BTK_WM_SIZE_HINT_P_MIN_SIZE | BTK_WM_SIZE_HINT_P_MAX_SIZE, .min_w = w->ms.w, .min_h = w->ms.h }; if (sc < 0) x_hints.max_w = w->ms.w; if (sr < 0) x_hints.max_h = w->ms.h; xcb_change_property(w->x_con, XCB_PROP_MODE_REPLACE, w->x_win, XCB_ATOM_WM_NORMAL_HINTS, XCB_ATOM_WM_SIZE_HINTS, 32, sizeof(btk_wm_size_hint_t), &x_hints); /* suscribe to WM_DELETE_WINDOW protocol from the wm */ xcb_intern_atom_cookie_t x_cookie_protocol = xcb_intern_atom(x_con, 0, strlen("WM_PROTOCOLS"), "WM_PROTOCOLS"); xcb_intern_atom_reply_t *x_reply_protocol = xcb_intern_atom_reply(x_con, x_cookie_protocol, NULL); xcb_intern_atom_cookie_t x_cookie_del = xcb_intern_atom(x_con, 0, strlen("WM_DELETE_WINDOW"), "WM_DELETE_WINDOW"); xcb_intern_atom_reply_t *x_reply_del = xcb_intern_atom_reply(x_con, x_cookie_del, NULL); xcb_change_property(x_con, XCB_PROP_MODE_REPLACE, w->x_win, x_reply_protocol->atom, XCB_ATOM_ATOM, 32, 1, &(x_reply_del->atom)); /* cairo setup */ w->c_srf = cairo_xcb_surface_create(x_con, w->x_win, x_vis, w->ps.w, w->ps.h); w->c_ctx = cairo_create(w->c_srf); /* setup font */ xcb_flush(x_con); cairo_surface_flush(w->c_srf); btk_log(BTK_LOG_WINDOW_CREATE); return w; } void btk_window_destroy(btk_window_t *w) { if (!w) { btk_log_warning(BTK_WARNING_WINDOW_DESTROY); return; } cairo_destroy(w->c_ctx); cairo_surface_destroy(w->c_srf); free(w->cells); free(w); btk_log(BTK_LOG_WINDOW_DESTROY); } void btk_window_disable(btk_window_t *w) { w->state |= BTK_WINDOW_STATE_DISABLED; btk_window_redraw(w); } void btk_window_enable(btk_window_t *w) { w->state &= ~BTK_WINDOW_STATE_DISABLED; btk_window_redraw(w); } void btk_window_init_cells(btk_window_t *w) { for (int i = 0; i < w->cells_n; i++) { btk_window_update_cell_area(w, &(w->cells[i])); } } void btk_window_input_button(btk_window_t *w, int button, int combo) { if ((w->state & BTK_WINDOW_STATE_DISABLED)) return; if (w->cell_focus < 0) return; btk_cell_t *c = &(w->cells[w->cell_focus]); if ((c->state & BTK_CELL_STATE_DISABLED) && button < 4) return; if (button == 1) { c->state |= BTK_CELL_STATE_PRESSED; w->cell_press = w->cell_focus; } /* relative pointer position the the pressed cell */ btk_pos_t rel_pt; rel_pt.x = w->pt.x - c->pa.x; rel_pt.y = w->pt.y - c->pa.y; /* forward press event to individual handlers for special actions */ switch (c->type) { case BTK_CELL_TYPE_BUTTON: btk_cell_button_trigger(c, button); break; case BTK_CELL_TYPE_INPUT: btk_cell_input_button(c, button, combo, rel_pt); break; case BTK_CELL_TYPE_LIST: btk_cell_list_input_button(c, button, combo, rel_pt); break; case BTK_CELL_TYPE_SWITCH: btk_cell_switch_toggle(c, button); break; case BTK_CELL_TYPE_TABLE: btk_cell_table_input_button(c, button, combo, rel_pt); break; case BTK_CELL_TYPE_EDITOR: btk_cell_editor_input_button(c, button, combo, rel_pt); break; default: break; } btk_cell_draw(c, w->c_ctx); draw_overlay(w, c->pa); } void btk_window_input_key(btk_window_t *w, xcb_keysym_t key) { if ((w->state & BTK_WINDOW_STATE_DISABLED)) return; if (w->cell_focus < 0) return; btk_cell_t *c = &(w->cells[w->cell_focus]); switch (c->type) { case BTK_CELL_TYPE_INPUT: btk_cell_input_input_key(c, (uint32_t)key); break; case BTK_CELL_TYPE_EDITOR: btk_cell_editor_input_key(c, (uint32_t)key); break; default: return; } btk_cell_draw(c, w->c_ctx); draw_overlay(w, c->pa); } /* updates the pointer position on the windows and the focused cell */ void btk_window_input_pointer(btk_window_t *w, btk_pos_t pos) { if ((w->state & BTK_WINDOW_STATE_DISABLED)) return; int prev_focus = w->cell_focus; w->pt.x = pos.x; w->pt.y = pos.y; /* iteration instead of calculation to avoid frame interference in the * case of thick frames */ for (int i = 0; i < w->cells_n; i++) { if (w->pt.x >= w->cells[i].pa.x && w->pt.y >= w->cells[i].pa.y && w->pt.x <= w->cells[i].pa.x + w->cells[i].pa.w && w->pt.y <= w->cells[i].pa.y + w->cells[i].pa.h) { w->cell_focus = i; break; } } /* if focus didn't change, leave */ if (w->cell_focus == prev_focus) return; /* unfocus previous cell if any */ if (prev_focus >= 0) { w->cells[prev_focus].state &= ~BTK_CELL_STATE_FOCUSED; btk_window_redraw_cell_focus(w, &(w->cells[prev_focus])); } /* focus new cell */ w->cells[w->cell_focus].state |= BTK_CELL_STATE_FOCUSED; btk_window_redraw_cell_focus(w, &(w->cells[w->cell_focus])); } void btk_window_redraw(btk_window_t *w) { /* clear surface to redraw */ cairo_set_source_rgb(w->c_ctx, btk_cl_frame.r, btk_cl_frame.g, btk_cl_frame.b); cairo_paint(w->c_ctx); /* hilight frame around focused cell if any */ if (w->cell_focus >= 0) { btk_rgb_t cl = w->focus_in >= 0 ? btk_cl_frame_in : btk_cl_frame_focus; cairo_set_source_rgb(w->c_ctx, cl.r, cl.g, cl.b); cairo_rectangle(w->c_ctx, w->cells[w->cell_focus].pa.x - btk_frame, w->cells[w->cell_focus].pa.y - btk_frame, w->cells[w->cell_focus].pa.w + 2 * btk_frame, w->cells[w->cell_focus].pa.h + 2 * btk_frame); cairo_fill(w->c_ctx); } /* render cells */ for (int i = 0; i < w->cells_n; i++) btk_cell_draw(&(w->cells[i]), w->c_ctx); /* disable overlay */ btk_area_t ov; ov.x = 0; ov.y = 0; ov.w = w->ps.w; ov.h = w->ps.h; draw_overlay(w, ov); cairo_surface_flush(w->c_srf); } /* to be used to regenretate cells externally */ void btk_window_redraw_cell(btk_window_t *w, unsigned int c_id) { if ((c_id) >= w->cells_n) { btk_log_warning(BTK_WARNING_OUT_OF_BOUND_CELL); return; } btk_cell_draw(&(w->cells[c_id]), w->c_ctx); draw_overlay(w, w->cells[c_id].pa); } /* only redraw a specific cell and not the whole window * unlike calling btk_winsow_redraw_cell, this function * also redraws the frame around the cell */ void btk_window_redraw_cell_focus(btk_window_t *w, btk_cell_t *c) { /* redraw frame */ btk_rgb_t cl; if ((c->state & BTK_CELL_STATE_IN)) { cl = btk_cl_frame_in; } else if ((c->state & BTK_CELL_STATE_FOCUSED)) { cl = btk_cl_frame_focus; } else { cl = btk_cl_frame; } cairo_set_source_rgb(w->c_ctx, cl.r, cl.g, cl.b); cairo_rectangle(w->c_ctx, c->pa.x - btk_frame, c->pa.y - btk_frame, c->pa.w + 2 * btk_frame, c->pa.h + 2 * btk_frame); cairo_fill(w->c_ctx); /* redraw cell */ btk_cell_draw(c, w->c_ctx); /* disable overlay */ btk_area_t ov; ov.x = c->pa.x - btk_frame; ov.y = c->pa.y - btk_frame; ov.w = c->pa.w + 2 * btk_frame; ov.h = c->pa.h + 2 * btk_frame; draw_overlay(w, ov); cairo_surface_flush(w->c_srf); } /* only called by an expose event */ int btk_window_resize(btk_window_t *w) { /* check if window geometry changed, update window size if it did */ xcb_get_geometry_reply_t *x_geom; xcb_get_geometry_cookie_t x_cookie = xcb_get_geometry(w->x_con, w->x_win); x_geom = xcb_get_geometry_reply(w->x_con, x_cookie, NULL); if (!x_geom) return 0; if (w->ps.w == x_geom->width && w->ps.h == x_geom->height) return 0; /* update window's geometry */ w->ps.w = x_geom->width; w->ps.h = x_geom->height; w->es.w = w->ps.w - w->ms.w; w->es.h = w->ps.h - w->ms.h; update_stretch_area(w); cairo_xcb_surface_set_size(w->c_srf, w->ps.w, w->ps.h); /* update cell's geometry */ /* only for those after the stretch col and row */ btk_cell_t *c; for (int i = 0; i < w->cells_n; i++) { c = &(w->cells[i]); if (c->ca.x + c->ca.w >= w->sc.x || c->ca.y + c->ca.h >= w->sc.y) btk_window_update_cell_area(w, c); } cairo_surface_flush(w->c_srf); btk_log(BTK_LOG_WINDOW_RESIZE); return 1; } void btk_window_set_name(btk_window_t *w, char *name) { xcb_change_property(w->x_con, XCB_PROP_MODE_REPLACE, w->x_win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, strlen(name), name); btk_log(BTK_LOG_WINDOW_RENAME); } void btk_window_update_cell_area(btk_window_t *w, btk_cell_t *c) { unsigned int ex, ey, ew, eh = 0; /* extra to account for stretched columns & rows */ ex = c->ca.x > w->sc.x ? w->es.w : 0; ey = c->ca.y > w->sc.y ? w->es.h : 0; ew = c->ca.x <= w->sc.x && c->ca.x + c->ca.w > w->sc.x ? w->es.w : 0; if (!(c->lh)) eh = c->ca.y <= w->sc.y && c->ca.y + c->ca.h > w->sc.y ? w->es.h : 0; c->pa.x = c->ca.x * (btk_cp.w + btk_frame) + btk_frame + ex; c->pa.y = c->ca.y * (btk_cp.h + btk_frame) + btk_frame + ey; c->pa.w = c->ca.w * (btk_cp.w + btk_frame) - btk_frame + ew; c->pa.h = c->ca.h * (btk_cp.h + btk_frame) - btk_frame + eh; /* field re-creation with correct size */ if (c->group == BTK_CELL_GROUP_FIELD) { if (c->fd) btk_cell_field_destroy(c->fd); c->fd = btk_cell_field_create(w->c_srf, c->pa); } /* special geometry depending on cell type */ switch (c->type) { case BTK_CELL_TYPE_TABLE: btk_cell_table_update_geometry(c); break; case BTK_CELL_TYPE_EDITOR: btk_cell_editor_update_text(c); break; default: break; } } /* add semi-transparent block overlay on areas to redraw if the window is disabled */ void draw_overlay(btk_window_t *w, btk_area_t a) { if (!(w->state & BTK_WINDOW_STATE_DISABLED)) return; if ((w->state & BTK_WINDOW_STATE_DISABLED)) { cairo_set_source_rgba(w->c_ctx, btk_cl_window_overlay.r, btk_cl_window_overlay.g, btk_cl_window_overlay.b, btk_cl_window_overlay.a); cairo_rectangle(w->c_ctx, a.x, a.y, a.w, a.h); cairo_fill(w->c_ctx); } } void update_stretch_area(btk_window_t *w) { w->sa.x = w->sc.x * (btk_cp.w + btk_frame); w->sa.y = w->sc.y * (btk_cp.h + btk_frame); w->sa.w = w->sa.x + w->es.w + btk_cp.w; w->sa.h = w->sa.x + w->es.h + btk_cp.h; }