ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/ledit.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
Log | Files | Refs | README | LICENSE

window.c (32063B)


      1 /* FIXME: replace the bottom bar with generic line entry
      2    -> most stuff can be copied from ltk, but I want to think about
      3    it a bit more because I don't want to have to maintain two copies
      4    of the code, so I want to first turn the necessary parts from ltk
      5    into a generic library that can be used here without modifying it */
      6 /* FIXME: check if xim handling still works with multiple windows */
      7 #include <time.h>
      8 #include <math.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <stdarg.h>
     12 
     13 #include <X11/Xlib.h>
     14 #include <X11/Xatom.h>
     15 #include <X11/Xutil.h>
     16 #include <X11/cursorfont.h>
     17 #include <pango/pangoxft.h>
     18 #include <pango/pango-utils.h> /* for PANGO_VERSION_CHECK */
     19 #include <X11/extensions/Xdbe.h>
     20 
     21 #include "util.h"
     22 #include "memory.h"
     23 #include "common.h"
     24 #include "txtbuf.h"
     25 #include "window.h"
     26 #include "macros.h"
     27 #include "config.h"
     28 #include "assert.h"
     29 #include "draw_util.h"
     30 #include "configparser.h"
     31 
     32 enum bb_itemtype {
     33 	BB_STR,
     34 	BB_MODE,
     35 	BB_HLMODE,
     36 	BB_LINE,
     37 	BB_BYTE,
     38 	BB_SEP,
     39 	BB_LANG
     40 };
     41 
     42 struct bb_item {
     43 	PangoLayout *layout;
     44 	ledit_draw *draw;
     45 	int w, h;
     46 	enum bb_itemtype type;
     47 	union {
     48 		int i;
     49 		ledit_mode m;
     50 		size_t sz;
     51 	} val;
     52 };
     53 
     54 /* FIXME: Everything to do with the bottom bar is extremely hacky */
     55 struct bottom_bar {
     56 	struct bb_item *items;
     57 	size_t items_num, items_alloc;
     58 	int w_per_sep; /* pixels used per separator in bottom bar */
     59 
     60 	/* message or editable text display */
     61 	PangoLayout *line;
     62 	ledit_draw *line_draw;
     63 	int line_w, line_h;
     64 	char *line_text;
     65 	int line_alloc, line_len;
     66 	int line_cur_pos; /* current position of cursor */
     67 	int min_pos;      /* minimum position cursor can be at */
     68 };
     69 
     70 /*
     71  * Recalculate the size of the actual text area (which is managed by the view).
     72  */
     73 static void recalc_text_size(ledit_window *window);
     74 
     75 /*
     76  * Get the position and height for the scrollbar handle.
     77  * 'height_ret' is set to the height (in pixels) of the scrollbar handle.
     78  * 'pos_ret' is set to the y position (in pixels) of the top of the scrollbar handle.
     79  */
     80 static void get_scroll_pos_height(ledit_window *windown, double *pos, double *height);
     81 
     82 /*
     83  * Set the scroll offset.
     84  * 'pos' is the pixel position of the top of the scrollbar handle.
     85  * This performs sanity checks and calls the scroll callback if set.
     86  */
     87 static void set_scroll_pos(ledit_window *window, double pos);
     88 
     89 /* FIXME: maybe just draw to one big draw instead of keeping all these small ones around */
     90 static void
     91 set_item_text(ledit_window *window, ledit_theme *theme, struct bb_item *item, char *text) {
     92 	pango_layout_set_text(item->layout, text, -1);
     93 	pango_layout_get_pixel_size(item->layout, &item->w, &item->h);
     94 	draw_grow(window, item->draw, item->w, item->h);
     95 	XftDrawRect(item->draw->xftdraw, &theme->bar_bg, 0, 0, item->w, item->h);
     96 	pango_xft_render_layout(item->draw->xftdraw, &theme->bar_fg, item->layout, 0, 0);
     97 }
     98 
     99 void
    100 window_set_format_args(ledit_window *window, ledit_mode mode, int hl_mode, size_t line, size_t byte, size_t lang_index) {
    101 	struct bb_item *items = window->bb->items;
    102 	char *text;
    103 	int changed = 0;
    104 	ledit_theme *theme = config_get_theme();
    105 	for (size_t i = 0; i < window->bb->items_num; i++) {
    106 		switch (items[i].type) {
    107 		case BB_MODE:
    108 			if (mode == items[i].val.m)
    109 				continue;
    110 			switch (mode) {
    111 			case NORMAL:
    112 				text = "Normal";
    113 				break;
    114 			case VISUAL:
    115 				text = "Visual";
    116 				break;
    117 			case INSERT:
    118 				text = "Insert";
    119 				break;
    120 			default:
    121 				text = "ledit is buggy";
    122 				break;
    123 			}
    124 			set_item_text(window, theme, &items[i], text);
    125 			changed = 1;
    126 			items[i].val.m = mode;
    127 			break;
    128 		case BB_HLMODE:
    129 			if (hl_mode == items[i].val.i)
    130 				continue;
    131 			if (hl_mode)
    132 				text = "HL";
    133 			else
    134 				text = "SL";
    135 			set_item_text(window, theme, &items[i], text);
    136 			changed = 1;
    137 			items[i].val.i = hl_mode;
    138 			break;
    139 		/* FIXME: avoid allocating new each time */
    140 		case BB_LINE:
    141 			if (line == items[i].val.sz)
    142 				continue;
    143 			text = print_fmt("%zu", line);
    144 			set_item_text(window, theme, &items[i], text);
    145 			free(text);
    146 			changed = 1;
    147 			items[i].val.sz = line;
    148 			break;
    149 		case BB_BYTE:
    150 			if (byte == items[i].val.sz)
    151 				continue;
    152 			text = print_fmt("%zu", byte);
    153 			set_item_text(window, theme, &items[i], text);
    154 			free(text);
    155 			changed = 1;
    156 			items[i].val.sz = byte;
    157 			break;
    158 		case BB_LANG:
    159 			if (lang_index == items[i].val.sz)
    160 				continue;
    161 			text = config_get_language_string(lang_index);
    162 			if (!text)
    163 				text = "(invalid language)";
    164 			set_item_text(window, theme, &items[i], text);
    165 			changed = 1;
    166 			items[i].val.sz = lang_index;
    167 			break;
    168 		default:
    169 			break;
    170 		}
    171 	}
    172 	if (changed) {
    173 		recalc_text_size(window);
    174 		window->redraw = 1;
    175 	}
    176 }
    177 
    178 /* FIXME: shouldn't window->bottom_text_shown also be true when message_shown? */
    179 /* FIXME: guard against negative width/height */
    180 static void
    181 recalc_text_size(ledit_window *window) {
    182 	ledit_theme *theme = config_get_theme();
    183 	int bar_h = 0;
    184 	int total_static_w = 0;
    185 	int num_sep = 0;
    186 	struct bb_item *items = window->bb->items;
    187 	for (size_t i = 0; i < window->bb->items_num; i++) {
    188 		if (items[i].type == BB_SEP) {
    189 			num_sep++;
    190 		} else {
    191 			total_static_w += items[i].w;
    192 			if (items[i].h > bar_h)
    193 				bar_h = items[i].h;
    194 		}
    195 	}
    196 	window->bb->w_per_sep = (window->w - total_static_w) / num_sep;
    197 	if (window->bottom_text_shown || window->message_shown)
    198 		bar_h = window->bb->line_h;
    199 	window->text_w = window->w - theme->scrollbar_width;
    200 	window->text_h = window->h - bar_h;
    201 	if (window->text_w < 0)
    202 		window->text_w = 0;
    203 	if (window->text_h < 0)
    204 		window->text_h = 0;
    205 }
    206 
    207 static void
    208 resize_line_text(ledit_window *window, int min_size) {
    209 	/* FIXME: use size_t everywhere */
    210 	ledit_assert(min_size >= 0);
    211 	size_t cap = ideal_array_size(window->bb->line_alloc, min_size);
    212 	if (cap > INT_MAX)
    213 		err_overflow();
    214 	if (cap != (size_t)window->bb->line_alloc) {
    215 		window->bb->line_alloc = (int)cap;
    216 		window->bb->line_text = ledit_realloc(window->bb->line_text, cap);
    217 	}
    218 }
    219 
    220 static void
    221 redraw_line_text(ledit_window *window) {
    222 	ledit_theme *theme = config_get_theme();
    223 	/* FIXME: set_text doesn't really belong here */
    224 	pango_layout_set_text(window->bb->line, window->bb->line_text, window->bb->line_len);
    225 	pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &window->bb->line_h);
    226 	draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h);
    227 	XftDrawRect(window->bb->line_draw->xftdraw, &theme->bar_bg, 0, 0, window->bb->line_w, window->bb->line_h);
    228 	pango_xft_render_layout(window->bb->line_draw->xftdraw, &theme->bar_fg, window->bb->line, 0, 0);
    229 	recalc_text_size(window);
    230 	window->redraw = 1;
    231 }
    232 
    233 /* FIXME: allow lines longer than window width to be displayed properly */
    234 void
    235 window_insert_bottom_bar_text(ledit_window *window, char *text, int len) {
    236 	ledit_assert(len >= -1);
    237 	ledit_assert(window->bb->line_cur_pos <= window->bb->line_len);
    238 
    239 	if (len == -1)
    240 		len = strlen(text);
    241 	/* \0 not included in len */
    242 	resize_line_text(window, window->bb->line_len + len + 1);
    243 	memmove(
    244 	    window->bb->line_text + window->bb->line_cur_pos + len,
    245 	    window->bb->line_text + window->bb->line_cur_pos,
    246 	    window->bb->line_len - window->bb->line_cur_pos
    247 	);
    248 	memcpy(window->bb->line_text + window->bb->line_cur_pos, text, len);
    249 	window->bb->line_len += len;
    250 	window->bb->line_text[window->bb->line_len] = '\0';
    251 	redraw_line_text(window);
    252 }
    253 
    254 void
    255 window_move_bottom_bar_cursor(ledit_window *window, int movement) {
    256 	ledit_assert(window->bb->line_cur_pos <= window->bb->line_len);
    257 	int trailing = 0;
    258 	int new_index = window->bb->line_cur_pos;
    259 	pango_layout_move_cursor_visually(
    260 	    window->bb->line, TRUE,
    261 	    new_index, trailing, movement,
    262 	    &new_index, &trailing
    263 	);
    264 	while (trailing > 0) {
    265 		trailing--;
    266 		/* FIXME: move to common/util */
    267 		new_index++;
    268 		while (new_index < window->bb->line_len &&
    269 		       (window->bb->line_text[new_index] & 0xC0) == 0x80)
    270 			new_index++;
    271 	}
    272 	if (new_index < window->bb->min_pos)
    273 		new_index = window->bb->min_pos;
    274 	if (new_index > window->bb->line_len)
    275 		new_index = window->bb->line_len;
    276 	window->bb->line_cur_pos = new_index;
    277 	window->redraw = 1;
    278 }
    279 
    280 void
    281 window_set_bottom_bar_min_pos(ledit_window *window, int pos) {
    282 	window->bb->min_pos = pos;
    283 }
    284 
    285 int
    286 window_get_bottom_bar_min_pos(ledit_window *window) {
    287 	return window->bb->min_pos;
    288 }
    289 
    290 void
    291 window_bottom_bar_cursor_to_beginning(ledit_window *window) {
    292 	window->bb->line_cur_pos = window->bb->min_pos;
    293 	window->redraw = 1;
    294 }
    295 
    296 void
    297 window_bottom_bar_cursor_to_end(ledit_window *window) {
    298 	window->bb->line_cur_pos = window->bb->line_len;
    299 	window->redraw = 1;
    300 }
    301 
    302 /* FIXME: respect PangoLogAttr.backspace_deletes_character */
    303 void
    304 window_delete_bottom_bar_char(ledit_window *window, int dir) {
    305 	int byte = window->bb->line_cur_pos;
    306 	if (dir < 0) {
    307 		byte--;
    308 		while (byte > 0 &&
    309 		       (window->bb->line_text[byte] & 0xC0) == 0x80) {
    310 			byte--;
    311 		}
    312 		if (byte < window->bb->min_pos)
    313 			byte = window->bb->min_pos;
    314 		memmove(
    315 		    window->bb->line_text + byte,
    316 		    window->bb->line_text + window->bb->line_cur_pos,
    317 		    window->bb->line_len - window->bb->line_cur_pos
    318 		);
    319 		window->bb->line_len -= (window->bb->line_cur_pos - byte);
    320 		window->bb->line_cur_pos = byte;
    321 	} else if (dir > 0) {
    322 		byte++;
    323 		while (byte < window->bb->line_len &&
    324 		       (window->bb->line_text[byte] & 0xC0) == 0x80) {
    325 			byte++;
    326 		}
    327 		if (byte >= window->bb->line_len)
    328 			byte = window->bb->line_len;
    329 		memmove(
    330 		    window->bb->line_text + window->bb->line_cur_pos,
    331 		    window->bb->line_text + byte,
    332 		    window->bb->line_len - byte
    333 		);
    334 		window->bb->line_len -= (byte - window->bb->line_cur_pos);
    335 	}
    336 	window->bb->line_text[window->bb->line_len] = '\0';
    337 	redraw_line_text(window);
    338 }
    339 
    340 void
    341 window_set_bottom_bar_cursor(ledit_window *window, int byte_pos) {
    342 	/* FIXME: check if valid? */
    343 	window->bb->line_cur_pos = byte_pos;
    344 	window->redraw = 1;
    345 }
    346 
    347 int
    348 ledit_window_get_bottom_bar_cursor(ledit_window *window) {
    349 	return window->bb->line_cur_pos;
    350 }
    351 
    352 void
    353 window_set_bottom_bar_text_shown(ledit_window *window, int shown) {
    354 	window->bottom_text_shown = shown;
    355 	window->redraw = 1;
    356 	if (shown) {
    357 		window->message_shown = 0;
    358 		pango_layout_set_width(window->bb->line, -1);
    359 		redraw_line_text(window);
    360 		recalc_text_size(window);
    361 	}
    362 }
    363 
    364 int
    365 ledit_window_bottom_bar_text_shown(ledit_window *window) {
    366 	return window->bottom_text_shown;
    367 }
    368 
    369 void
    370 window_set_bottom_bar_text(ledit_window *window, char *text, int len) {
    371 	window->bb->line_len = 0;
    372 	window->bb->line_cur_pos = 0;
    373 	window_insert_bottom_bar_text(window, text, len);
    374 	window->redraw = 1;
    375 }
    376 
    377 void
    378 window_set_bottom_bar_realtext(ledit_window *window, char *text, int len) {
    379 	if (window->bb->min_pos <= window->bb->line_len)
    380 		window->bb->line_len = window->bb->min_pos;
    381 	window->bb->line_cur_pos = window->bb->line_len;
    382 	window_insert_bottom_bar_text(window, text, len);
    383 	window->redraw = 1;
    384 }
    385 
    386 char *
    387 window_get_bottom_bar_text(ledit_window *window) {
    388 	return window->bb->line_text;
    389 }
    390 
    391 void
    392 window_show_message(ledit_window *window, char *text, int len) {
    393 	pango_layout_set_width(window->bb->line, window->w * PANGO_SCALE);
    394 	window_set_bottom_bar_text(window, text, len);
    395 	/* FIXME: rename these */
    396 	window->bottom_text_shown = 0;
    397 	window->message_shown = 1;
    398 	window->redraw = 1;
    399 }
    400 
    401 void
    402 window_show_message_fmt(ledit_window *window, char *fmt, ...) {
    403 	va_list args;
    404 	va_start(args, fmt);
    405 	int len = vsnprintf(window->bb->line_text, window->bb->line_alloc, fmt, args);
    406 	if (len >= window->bb->line_alloc) {
    407 		va_end(args);
    408 		va_start(args, fmt);
    409 		/* +1 because of terminating '\0' */
    410 		resize_line_text(window, len + 1);
    411 		vsnprintf(window->bb->line_text, window->bb->line_alloc, fmt, args);
    412 	}
    413 	window->bb->line_len = len;
    414 	va_end(args);
    415 	pango_layout_set_width(window->bb->line, window->w * PANGO_SCALE);
    416 	window->bottom_text_shown = 0;
    417 	window->message_shown = 1;
    418 	redraw_line_text(window);
    419 }
    420 
    421 int
    422 window_message_shown(ledit_window *window) {
    423 	return window->message_shown;
    424 }
    425 
    426 void
    427 window_hide_message(ledit_window *window) {
    428 	window->message_shown = 0;
    429 	window->redraw = 1;
    430 	recalc_text_size(window);
    431 }
    432 
    433 /* FIXME: give these functions more sensible names */
    434 static void
    435 get_scroll_pos_height(ledit_window *window, double *pos_ret, double *height_ret) {
    436 	*height_ret = ((double)window->text_h / window->scroll_max) * window->text_h;
    437 	*pos_ret = (window->scroll_offset /
    438 	       (window->scroll_max - window->text_h)) * (window->text_h - *height_ret);
    439 }
    440 
    441 static void
    442 set_scroll_pos(ledit_window *window, double pos) {
    443 	window->scroll_offset = pos * (window->scroll_max / (double)window->text_h);
    444 	if (window->scroll_offset < 0)
    445 		window->scroll_offset = 0;
    446 	if (window->scroll_offset + window->text_h > window->scroll_max)
    447 		window->scroll_offset = window->scroll_max - window->text_h;
    448 	/* FIXME: check for overflow */
    449 	if (window->scroll_callback)
    450 		window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset);
    451 	window->redraw = 1;
    452 }
    453 
    454 void
    455 window_set_scroll_max(ledit_window *window, long max) {
    456 	window->scroll_max = max;
    457 	window->redraw = 1;
    458 }
    459 
    460 void
    461 window_set_scroll_pos(ledit_window *window, long pos) {
    462 	window->scroll_offset = pos;
    463 	window->redraw = 1;
    464 }
    465 
    466 void
    467 window_set_scroll_callback(ledit_window *window, void (*cb)(void *, long), void *data) {
    468 	window->scroll_callback = cb;
    469 	window->scroll_cb_data = data;
    470 }
    471 
    472 void
    473 window_set_button_callback(ledit_window *window, void (*cb)(void *, XEvent *), void *data) {
    474 	window->button_callback = cb;
    475 	window->button_cb_data = data;
    476 }
    477 
    478 void
    479 window_set_resize_callback(ledit_window *window, void (*cb)(void *), void *data) {
    480 	window->resize_callback = cb;
    481 	window->resize_cb_data = data;
    482 }
    483 
    484 /* FIXME: Change naming convention to fit the rest of ledit */
    485 /* FIXME: It's a bit weird when an input box pops up during normal mode.
    486    Can/should this be disabled? */
    487 int ximopen(ledit_window *window, Display *dpy);
    488 void ximinstantiate(Display *dpy, XPointer client, XPointer call);
    489 void ximdestroy(XIM xim, XPointer client, XPointer call);
    490 int xicdestroy(XIC xim, XPointer client, XPointer call);
    491 
    492 /* blatantly stolen from st */
    493 int
    494 ximopen(ledit_window *window, Display *dpy)
    495 {
    496 	XIMCallback imdestroy = { .client_data = (XPointer)window, .callback = ximdestroy };
    497 	XICCallback icdestroy = { .client_data = (XPointer)window, .callback = xicdestroy };
    498 
    499 	window->xim = XOpenIM(dpy, NULL, NULL, NULL);
    500 	if (window->xim == NULL)
    501 		return 0;
    502 
    503 	if (XSetIMValues(window->xim, XNDestroyCallback, &imdestroy, NULL)) {
    504 		fprintf(
    505 		    stderr, "XSetIMValues: Could not set XNDestroyCallback.\n"
    506 		);
    507 	}
    508 
    509 	window->spotlist = XVaCreateNestedList(0, XNSpotLocation, &window->spot, NULL);
    510 
    511 	if (window->xic == NULL) {
    512 		window->xic = XCreateIC(
    513 		    window->xim, XNInputStyle,
    514 		    XIMPreeditNothing | XIMStatusNothing,
    515 		    XNClientWindow, window->xwin,
    516 		    XNDestroyCallback, &icdestroy, NULL
    517 		);
    518 	}
    519 	if (window->xic == NULL)
    520 		fprintf(stderr, "XCreateIC: Could not create input context.\n");
    521 
    522 	return 1;
    523 }
    524 
    525 void
    526 ximinstantiate(Display *dpy, XPointer client, XPointer call)
    527 {
    528 	(void)call;
    529 	ledit_window *window = (ledit_window *)client;
    530 	if (ximopen(window, dpy)) {
    531 		XUnregisterIMInstantiateCallback(
    532 		    dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window
    533 		);
    534 	}
    535 }
    536 
    537 void
    538 ximdestroy(XIM xim, XPointer client, XPointer call)
    539 {
    540 	(void)xim;
    541 	(void)call;
    542 	ledit_window *window = (ledit_window *)client;
    543 	window->xim = NULL;
    544 	XRegisterIMInstantiateCallback(
    545 	    window->common->dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window
    546 	);
    547 	XFree(window->spotlist);
    548 }
    549 
    550 int
    551 xicdestroy(XIC xim, XPointer client, XPointer call)
    552 {
    553 	(void)xim;
    554 	(void)call;
    555 	ledit_window *window = (ledit_window *)client;
    556 	window->xic = NULL;
    557 	return 1;
    558 }
    559 
    560 void
    561 xximspot(ledit_window *window, int x, int y) {
    562 	if (window->xic == NULL)
    563 		return;
    564 	/* FIXME! */
    565 	window->spot.x = x;
    566 	window->spot.y = y;
    567 	XSetICValues(window->xic, XNPreeditAttributes, window->spotlist, NULL);
    568 }
    569 
    570 static struct bb_item *
    571 push_bb_item(ledit_window *window) {
    572 	if (window->bb->items_num == window->bb->items_alloc) {
    573 		size_t new_alloc = ideal_array_size(window->bb->items_alloc, add_sz(window->bb->items_num, 1));
    574 		window->bb->items = ledit_reallocarray(window->bb->items, new_alloc, sizeof(struct bb_item));
    575 		window->bb->items_alloc = new_alloc;
    576 	}
    577 	struct bb_item *item = &window->bb->items[window->bb->items_num];
    578 	item->layout = pango_layout_new(window->context);
    579 	pango_layout_set_font_description(item->layout, window->font);
    580 	item->draw = draw_create(window, 10, 10);
    581 	item->w = item->h = 0;
    582 	window->bb->items_num++;
    583 	return item;
    584 }
    585 
    586 ledit_window *
    587 window_create(ledit_common *common, ledit_clipboard *clipboard) {
    588 	XGCValues gcv;
    589 
    590 	ledit_theme *theme = config_get_theme();
    591 
    592 	ledit_window *window = ledit_malloc(sizeof(ledit_window));
    593 	window->first_resize = 1;
    594 
    595 	window->scroll_dragging = 0;
    596 	window->scroll_grab_handle = 0;
    597 	window->w = 500;
    598 	window->h = 500;
    599 	window->scroll_callback = NULL;
    600 	window->button_callback = NULL;
    601 	window->resize_callback = NULL;
    602 	window->scroll_cb_data = NULL;
    603 	window->button_cb_data = NULL;
    604 	window->resize_cb_data = NULL;
    605 
    606 	memset(&window->wattrs, 0, sizeof(window->wattrs));
    607 	window->wattrs.background_pixel = theme->text_bg.pixel;
    608 	window->wattrs.colormap = common->cm;
    609 	/* this causes the window contents to be kept
    610 	 * when it is resized, leading to less flicker */
    611 	window->wattrs.bit_gravity = NorthWestGravity;
    612 	/* FIXME: FocusChangeMask? */
    613 	window->wattrs.event_mask = KeyPressMask |
    614 	    ExposureMask | VisibilityChangeMask | StructureNotifyMask |
    615 	    PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
    616 	window->xwin = XCreateWindow(
    617 	    common->dpy, DefaultRootWindow(common->dpy), 0, 0,
    618 	    window->w, window->h, 0, common->depth,
    619 	    InputOutput, common->vis,
    620 	    CWBackPixel | CWColormap | CWBitGravity | CWEventMask, &window->wattrs
    621 	);
    622 	XSetStandardProperties(common->dpy, window->xwin, "ledit", NULL, None, NULL, 0, NULL);
    623 
    624 	window->back_buf = XdbeAllocateBackBufferName(
    625 	    common->dpy, window->xwin, XdbeBackground
    626 	);
    627 	window->drawable = window->back_buf;
    628 
    629 	memset(&gcv, 0, sizeof(gcv));
    630 	gcv.line_width = 1;
    631 	window->gc = XCreateGC(common->dpy, window->back_buf, GCLineWidth, &gcv);
    632 
    633 	/* FIXME: move to common */
    634 	window->fontmap = pango_xft_get_font_map(common->dpy, common->screen);
    635 	window->context = pango_font_map_create_context(window->fontmap);
    636 
    637 	window->font = pango_font_description_from_string(theme->text_font);
    638 	pango_font_description_set_size(window->font, theme->text_size * PANGO_SCALE);
    639 
    640 	window->wm_delete_msg = XInternAtom(common->dpy, "WM_DELETE_WINDOW", False);
    641 	XSetWMProtocols(common->dpy, window->xwin, &window->wm_delete_msg, 1);
    642 
    643 	window->common = common;
    644 	/* FIXME: not used yet - this will be used later when clipboard support is added to the bottom bar */
    645 	window->clipboard = clipboard;
    646 
    647 	window->bb = ledit_malloc(sizeof(bottom_bar));
    648 	window->bb->items = NULL;
    649 	window->bb->items_num = window->bb->items_alloc = 0;
    650 	window->bb->w_per_sep = 0;
    651 	window->bb->line = pango_layout_new(window->context);
    652 	pango_layout_set_font_description(window->bb->line, window->font);
    653 	pango_layout_set_wrap(window->bb->line, PANGO_WRAP_WORD_CHAR);
    654 	#if PANGO_VERSION_CHECK(1, 44, 0)
    655 	PangoAttrList *pattrs = pango_attr_list_new();
    656 	PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE);
    657 	pango_attr_list_insert(pattrs, no_hyphens);
    658 	pango_layout_set_attributes(window->bb->line, pattrs);
    659 	pango_attr_list_unref(pattrs);
    660 	#endif
    661 	window->bb->line_draw = draw_create(window, 10, 10);
    662 	window->bb->line_w = window->bb->line_h = 10;
    663 	window->bb->line_text = NULL;
    664 	window->bb->line_alloc = window->bb->line_len = 0;
    665 	window->bb->line_cur_pos = 0;
    666 	window->bb->min_pos = 0;
    667 	window->bottom_text_shown = 0;
    668 	window->message_shown = 0;
    669 
    670 	window->xim = NULL;
    671 	window->xic = NULL;
    672 	if (!ximopen(window, common->dpy)) {
    673 		XRegisterIMInstantiateCallback(
    674 		    common->dpy, NULL, NULL, NULL,
    675 		    ximinstantiate, (XPointer)window
    676 		);
    677 	}
    678 
    679 	XMapWindow(common->dpy, window->xwin);
    680 
    681 	window->cursor_text = XCreateFontCursor(window->common->dpy, XC_xterm);
    682 	/* FIXME: maybe delay this (i.e. move to different function)? */
    683 	XMapWindow(common->dpy, window->xwin);
    684 
    685 	window->redraw = 1;
    686 	struct timespec now;
    687 	clock_gettime(CLOCK_MONOTONIC, &now);
    688 	window->last_scroll = now;
    689 	window->last_motion = now;
    690 	window->last_resize = now;
    691 	window->last_scroll_valid = 0;
    692 	window->last_motion_valid = 0;
    693 	window->last_resize_valid = 0;
    694 	window->scroll_num = 0;
    695 
    696 	/* setup format for bottom bar */
    697 	/* FIXME: this seems ugly, there's probably a better way
    698 	          also, it might still be buggy */
    699 	char *fmt = ledit_strdup(theme->bar_fmt);
    700 	int offset = 0;
    701 	int in_text = 0;
    702 	int start = 0;
    703 	size_t i = 0;
    704 	struct bb_item *item = NULL;
    705 	for (; fmt[i] != '\0'; i++) {
    706 		if (fmt[i] == '%') {
    707 			i++;
    708 			if (fmt[i] == '%') {
    709 				if (!in_text) {
    710 					start = i;
    711 					offset = 0;
    712 					in_text = 1;
    713 				} else {
    714 					offset++;
    715 				}
    716 			} else {
    717 				if (in_text) {
    718 					item = push_bb_item(window);
    719 					item->type = BB_STR;
    720 					fmt[i - 1 - offset] = '\0';
    721 					set_item_text(window, theme, item, fmt + start);
    722 					in_text = 0;
    723 				}
    724 				switch (fmt[i]) {
    725 				case 'l':
    726 					item = push_bb_item(window);
    727 					item->type = BB_LINE;
    728 					item->val.sz = SIZE_MAX;
    729 					break;
    730 				case 'b':
    731 					item = push_bb_item(window);
    732 					item->type = BB_BYTE;
    733 					item->val.sz = SIZE_MAX;
    734 					break;
    735 				case 'k':
    736 					item = push_bb_item(window);
    737 					item->type = BB_LANG;
    738 					item->val.sz = SIZE_MAX;
    739 					break;
    740 				case 'm':
    741 					item = push_bb_item(window);
    742 					item->type = BB_MODE;
    743 					item->val.m = VISUAL;
    744 					break;
    745 				case 'h':
    746 					item = push_bb_item(window);
    747 					item->type = BB_HLMODE;
    748 					item->val.i = -1;
    749 					break;
    750 				case 's':
    751 					/* FIXME: don't create layout and draw for this */
    752 					item = push_bb_item(window);
    753 					item->type = BB_SEP;
    754 					break;
    755 				default:
    756 					/* FIXME: better error reporting (also shown in window); sane behavior here */
    757 					fprintf(stderr, "WARNING: Invalid format string for bottom bar.\n");
    758 					window_show_message(window, "Invalid format string for bottom bar.", -1);
    759 					/* FIXME: it might make more sense to just add the character as a literal,
    760 					   but this is the easiest */
    761 					goto end;
    762 				}
    763 			}
    764 		} else if (!in_text) {
    765 			start = i;
    766 			offset = 0;
    767 			in_text = 1;
    768 		}
    769 		fmt[i - offset] = fmt[i];
    770 	}
    771 	if (in_text) {
    772 		item = push_bb_item(window);
    773 		item->type = BB_STR;
    774 		fmt[i - offset] = '\0';
    775 		set_item_text(window, theme, item, fmt + start);
    776 	}
    777 end:
    778 	free(fmt);
    779 	window_set_format_args(window, NORMAL, 1, 1, 1, 0);
    780 
    781 	return window;
    782 }
    783 
    784 void
    785 window_destroy(ledit_window *window) {
    786 	struct bb_item *items = window->bb->items;
    787 	for (size_t i = 0; i < window->bb->items_num; i++) {
    788 		if (items[i].layout)
    789 			g_object_unref(items[i].layout);
    790 		if (items[i].draw)
    791 			draw_destroy(window, items[i].draw);
    792 	}
    793 	free(window->bb->items);
    794 	/* FIXME: check what's still missing */
    795 	g_object_unref(window->bb->line);
    796 	draw_destroy(window, window->bb->line_draw);
    797 
    798 	pango_font_description_free(window->font);
    799 	/* FIXME: The pango documentation says that the context must be freed,
    800 	   but the program segfaults when that is done. */
    801 	/*g_object_unref(window->context);*/
    802 	g_object_unref(window->fontmap);
    803 
    804 	XFreeGC(window->common->dpy, window->gc);
    805 	if (window->spotlist)
    806 		XFree(window->spotlist);
    807 	XDestroyWindow(window->common->dpy, window->xwin);
    808 
    809 	free(window->bb->line_text);
    810 	free(window->bb);
    811 	free(window);
    812 }
    813 
    814 void
    815 window_clear(ledit_window *window) {
    816 	ledit_theme *theme = config_get_theme();
    817 	XSetForeground(window->common->dpy, window->gc, theme->text_bg.pixel);
    818 	XFillRectangle(
    819 	    window->common->dpy, window->drawable, window->gc, 0, 0, window->w, window->h
    820 	);
    821 }
    822 
    823 void
    824 window_redraw(ledit_window *window) {
    825 	ledit_theme *t = config_get_theme();
    826 	if (window->scroll_max > window->text_h) {
    827 		XSetForeground(window->common->dpy, window->gc, t->scrollbar_bg.pixel);
    828 		XFillRectangle(
    829 		    window->common->dpy, window->drawable, window->gc,
    830 		    window->w - t->scrollbar_width, 0, t->scrollbar_width, window->text_h
    831 		);
    832 		XSetForeground(window->common->dpy, window->gc, t->scrollbar_fg.pixel);
    833 		double scroll_h, scroll_y;
    834 		get_scroll_pos_height(window, &scroll_y, &scroll_h);
    835 		XFillRectangle(
    836 		    window->common->dpy, window->drawable, window->gc,
    837 		    window->w - t->scrollbar_width, (int)round(scroll_y),
    838 		    t->scrollbar_width, (int)round(scroll_h)
    839 		);
    840 	}
    841 	XSetForeground(window->common->dpy, window->gc, t->bar_bg.pixel);
    842 	XFillRectangle(
    843 	    window->common->dpy, window->drawable, window->gc,
    844 	    0, window->text_h,
    845 	    window->w, window->h - window->text_h
    846 	);
    847 	if (window->message_shown) {
    848 		XCopyArea(
    849 		    window->common->dpy, window->bb->line_draw->pixmap,
    850 		    window->drawable, window->gc,
    851 		    0, 0, window->bb->line_w, window->bb->line_h,
    852 		    0, window->text_h
    853 		);
    854 	} else if (window->bottom_text_shown) {
    855 		XSetForeground(window->common->dpy, window->gc, t->bar_cursor.pixel);
    856 		/* move input method position to cursor and draw cursor */
    857 		PangoRectangle strong, weak;
    858 		pango_layout_get_cursor_pos(
    859 		    window->bb->line, window->bb->line_cur_pos, &strong, &weak
    860 		);
    861 		/* FIXME: Is this the best position? The bottom of the lins is
    862 		   just the bottom of the window, so an input method box will
    863 		   have to be moved out of the way anyways (fcitx just moves it
    864 		   up a bit so it sort of works) */
    865 		xximspot(window, strong.x / PANGO_SCALE, window->h);
    866 		int x = 0;
    867 		int w = window->bb->line_w;
    868 		int cur_x = strong.x / PANGO_SCALE;
    869 		if (w > window->w) {
    870 			/* FIXME: try to keep some space on the edges */
    871 			x = (cur_x / window->w) * window->w;
    872 			w = window->w;
    873 			if (x + w > window->bb->line_w)
    874 				w = window->bb->line_w - x;
    875 		}
    876 		XCopyArea(
    877 		    window->common->dpy, window->bb->line_draw->pixmap,
    878 		    window->drawable, window->gc,
    879 		    x, 0, w, window->bb->line_h,
    880 		    0, window->text_h
    881 		);
    882 		XDrawLine(
    883 		    window->common->dpy, window->drawable, window->gc,
    884 		    cur_x - x, window->text_h + strong.y / PANGO_SCALE,
    885 		    cur_x - x,
    886 		    window->text_h + (strong.y + strong.height) / PANGO_SCALE
    887 		);
    888 	} else {
    889 		struct bb_item *items = window->bb->items;
    890 		int cur_x = 0;
    891 		for (size_t i = 0; i < window->bb->items_num; i++) {
    892 			if (items[i].type == BB_SEP) {
    893 				cur_x += window->bb->w_per_sep;
    894 			} else {
    895 				XCopyArea(
    896 				    window->common->dpy, items[i].draw->pixmap,
    897 				    window->drawable, window->gc,
    898 				    0, 0, items[i].w, items[i].h,
    899 				    cur_x, window->text_h
    900 				);
    901 				cur_x += items[i].w;
    902 			}
    903 		}
    904 	}
    905 
    906 	XdbeSwapInfo swap_info;
    907 	swap_info.swap_window = window->xwin;
    908 	swap_info.swap_action = XdbeBackground;
    909 
    910 	if (!XdbeSwapBuffers(window->common->dpy, &swap_info, 1))
    911 		exit(1);
    912 	XFlush(window->common->dpy);
    913 	window->redraw = 0;
    914 }
    915 
    916 void
    917 window_get_textview_size(ledit_window *window, int *w_ret, int *h_ret) {
    918 	*w_ret = window->text_w;
    919 	*h_ret = window->text_h;
    920 }
    921 
    922 void
    923 window_handle_filtered_events(ledit_window *window) {
    924 	struct timespec now, elapsed;
    925 	if (window->last_motion_valid) {
    926 		clock_gettime(CLOCK_MONOTONIC, &now);
    927 		ledit_timespecsub(&now, &window->last_motion, &elapsed);
    928 		if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) {
    929 			window_drag_motion(window, &window->last_motion_event);
    930 			window->last_motion = now;
    931 			window->last_motion_valid = 0;
    932 		}
    933 	}
    934 	if (window->last_scroll_valid) {
    935 		clock_gettime(CLOCK_MONOTONIC, &now);
    936 		ledit_timespecsub(&now, &window->last_scroll, &elapsed);
    937 		if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) {
    938 			window_button_press(window, &window->last_scroll_event, window->scroll_num);
    939 			window->last_scroll = now;
    940 			window->last_scroll_valid = 0;
    941 		}
    942 	}
    943 	if (window->last_resize_valid) {
    944 		clock_gettime(CLOCK_MONOTONIC, &now);
    945 		ledit_timespecsub(&now, &window->last_resize, &elapsed);
    946 		if (window->first_resize || elapsed.tv_sec > 0 || elapsed.tv_nsec >= RESIZE_TICK) {
    947 			window_resize(
    948 			    window,
    949 			    window->last_resize_event.xconfigure.width,
    950 			    window->last_resize_event.xconfigure.height
    951 			);
    952 			window->last_resize = now;
    953 			window->last_resize_valid = 0;
    954 			window->redraw = 1;
    955 			window->first_resize = 0;
    956 		}
    957 	}
    958 }
    959 
    960 void
    961 window_resize(ledit_window *window, int w, int h) {
    962 	if (w == window->w && h == window->h)
    963 		return;
    964 	window->w = w;
    965 	window->h = h;
    966 	if (window->message_shown) {
    967 		pango_layout_set_width(window->bb->line, window->w * PANGO_SCALE);
    968 		redraw_line_text(window);
    969 	} else {
    970 		recalc_text_size(window);
    971 	}
    972 	if (window->resize_callback)
    973 		window->resize_callback(window->resize_cb_data);
    974 	window->redraw = 1;
    975 }
    976 
    977 void
    978 window_register_button_press(ledit_window *window, XEvent *event) {
    979 	int scroll_delta;
    980 	if (event->xbutton.button == Button4 ||
    981 	    event->xbutton.button == Button5) {
    982 		scroll_delta = event->xbutton.button == Button4 ? -1 : 1;
    983 		if (window->last_scroll_valid) {
    984 			window->scroll_num += scroll_delta;
    985 		} else {
    986 			window->last_scroll_event = *event;
    987 			window->last_scroll_valid = 1;
    988 			window->scroll_num = scroll_delta;
    989 		}
    990 	} else {
    991 		window_button_press(window, event, 0);
    992 	}
    993 }
    994 
    995 void
    996 window_register_resize(ledit_window *window, XEvent *event) {
    997 	window->last_resize_event = *event;
    998 	window->last_resize_valid = 1;
    999 }
   1000 
   1001 void
   1002 window_register_motion(ledit_window *window, XEvent *event) {
   1003 	/* cursor should always change, even if time has not elapsed */
   1004 	int x = event->xmotion.x;
   1005 	int y = event->xmotion.y;
   1006 	/* FIXME: avoid these calls if nothing has changed */
   1007 	if (x < window->text_w && y < window->text_h)
   1008 		XDefineCursor(window->common->dpy, window->xwin, window->cursor_text);
   1009 	else
   1010 		XDefineCursor(window->common->dpy, window->xwin, None);
   1011 	window->last_motion_event = *event;
   1012 	window->last_motion_valid = 1;
   1013 }
   1014 
   1015 /* FIXME: make button handling more consistent */
   1016 /* FIXME: improve set_scroll_pos; make it a bit clearer */
   1017 void
   1018 window_button_press(ledit_window *window, XEvent *event, int scroll_num) {
   1019 	ledit_theme *theme = config_get_theme();
   1020 	int x = event->xbutton.x;
   1021 	int y = event->xbutton.y;
   1022 	double scroll_h, scroll_y;
   1023 	switch (event->xbutton.button) {
   1024 		case Button1:
   1025 			get_scroll_pos_height(window, &scroll_y, &scroll_h);
   1026 			if (x >= window->text_w) {
   1027 				window->scroll_dragging = 1;
   1028 				window->scroll_grab_handle = y;
   1029 				if (y < scroll_y || y > scroll_y + scroll_h) {
   1030 					double new_scroll_y = y - scroll_h / 2;
   1031 					set_scroll_pos(window, new_scroll_y);
   1032 				}
   1033 				window->redraw = 1;
   1034 			} else if (y < window->text_h) {
   1035 				if (window->button_callback)
   1036 					window->button_callback(window->button_cb_data, event);
   1037 				window->redraw = 1;
   1038 			}
   1039 			break;
   1040 		case Button2:
   1041 			if (x < window->text_w && y < window->text_h && window->button_callback)
   1042 				window->button_callback(window->button_cb_data, event);
   1043 			break;
   1044 		case Button4:
   1045 		case Button5:
   1046 			window->scroll_offset += scroll_num * theme->scrollbar_step;
   1047 			if (window->scroll_offset < 0)
   1048 				window->scroll_offset = 0;
   1049 			if (window->scroll_offset + window->text_h > window->scroll_max) {
   1050 				window->scroll_offset = window->scroll_max - window->text_h;
   1051 			}
   1052 			if (window->scroll_callback)
   1053 				window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset);
   1054 			window->redraw = 1;
   1055 	}
   1056 }
   1057 
   1058 void
   1059 window_button_release(ledit_window *window, XEvent *event) {
   1060 	int x = event->xbutton.x;
   1061 	int y = event->xbutton.y;
   1062 	int in_text = x < window->text_w && y < window->text_h;
   1063 	if (event->xbutton.button == Button1) {
   1064 		window->scroll_dragging = 0;
   1065 		if (in_text && window->button_callback)
   1066 			window->button_callback(window->button_cb_data, event);
   1067 		window->redraw = 1;
   1068 	} else if (event->xbutton.button == Button2 && in_text && window->button_callback) {
   1069 		window->button_callback(window->button_cb_data, event);
   1070 	}
   1071 }
   1072 
   1073 void
   1074 window_drag_motion(ledit_window *window, XEvent *event) {
   1075 	if (window->scroll_dragging) {
   1076 		double scroll_h, scroll_y;
   1077 		get_scroll_pos_height(window, &scroll_y, &scroll_h);
   1078 		scroll_y += event->xmotion.y - window->scroll_grab_handle;
   1079 		window->scroll_grab_handle = event->xmotion.y;
   1080 		set_scroll_pos(window, scroll_y);
   1081 		window->redraw = 1;
   1082 	} else  {
   1083 		if (window->button_callback)
   1084 			window->button_callback(window->button_cb_data, event);
   1085 		window->redraw = 1;
   1086 	}
   1087 }