477 lines
12 KiB
C
477 lines
12 KiB
C
|
#include <cairo/cairo.h>
|
||
|
#include <cairo/cairo-xcb.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <xcb/xcb.h>
|
||
|
|
||
|
#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;
|
||
|
}
|