window.c - 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/… | |
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… | |
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 scro… | |
79 */ | |
80 static void get_scroll_pos_height(ledit_window *windown, double *pos, do… | |
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 s… | |
90 static void | |
91 set_item_text(ledit_window *window, ledit_theme *theme, struct bb_item *… | |
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, … | |
96 pango_xft_render_layout(item->draw->xftdraw, &theme->bar_fg, ite… | |
97 } | |
98 | |
99 void | |
100 window_set_format_args(ledit_window *window, ledit_mode mode, int hl_mod… | |
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_… | |
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_t… | |
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, w… | |
225 pango_layout_get_pixel_size(window->bb->line, &window->bb->line_… | |
226 draw_grow(window, window->bb->line_draw, window->bb->line_w, win… | |
227 XftDrawRect(window->bb->line_draw->xftdraw, &theme->bar_bg, 0, 0… | |
228 pango_xft_render_layout(window->bb->line_draw->xftdraw, &theme->… | |
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, l… | |
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_allo… | |
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,… | |
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 *hei… | |
436 *height_ret = ((double)window->text_h / window->scroll_max) * wi… | |
437 *pos_ret = (window->scroll_offset / | |
438 (window->scroll_max - window->text_h)) * (window->text_h … | |
439 } | |
440 | |
441 static void | |
442 set_scroll_pos(ledit_window *window, double pos) { | |
443 window->scroll_offset = pos * (window->scroll_max / (double)wind… | |
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->tex… | |
448 /* FIXME: check for overflow */ | |
449 if (window->scroll_callback) | |
450 window->scroll_callback(window->scroll_cb_data, (long)wi… | |
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… | |
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 *, XEve… | |
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 *), voi… | |
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, .call… | |
497 XICCallback icdestroy = { .client_data = (XPointer)window, .call… | |
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, NUL… | |
504 fprintf( | |
505 stderr, "XSetIMValues: Could not set XNDestroyCallba… | |
506 ); | |
507 } | |
508 | |
509 window->spotlist = XVaCreateNestedList(0, XNSpotLocation, &windo… | |
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 conte… | |
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)win… | |
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, (XPoi… | |
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,… | |
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_al… | |
574 window->bb->items = ledit_reallocarray(window->bb->items… | |
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, &wind… | |
621 ); | |
622 XSetStandardProperties(common->dpy, window->xwin, "ledit", 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, GCLineWidt… | |
632 | |
633 /* FIXME: move to common */ | |
634 window->fontmap = pango_xft_get_font_map(common->dpy, common->sc… | |
635 window->context = pango_font_map_create_context(window->fontmap); | |
636 | |
637 window->font = pango_font_description_from_string(theme->text_fo… | |
638 pango_font_description_set_size(window->font, theme->text_size *… | |
639 | |
640 window->wm_delete_msg = XInternAtom(common->dpy, "WM_DELETE_WIND… | |
641 XSetWMProtocols(common->dpy, window->xwin, &window->wm_delete_ms… | |
642 | |
643 window->common = common; | |
644 /* FIXME: not used yet - this will be used later when clipboard … | |
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_… | |
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, ite… | |
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 an… | |
752 item = push_bb_item(window); | |
753 item->type = BB_SEP; | |
754 break; | |
755 default: | |
756 /* FIXME: better error reporting… | |
757 fprintf(stderr, "WARNING: Invali… | |
758 window_show_message(window, "Inv… | |
759 /* FIXME: it might make more sen… | |
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 … | |
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.p… | |
818 XFillRectangle( | |
819 window->common->dpy, window->drawable, window->gc, 0, 0, win… | |
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->scrol… | |
828 XFillRectangle( | |
829 window->common->dpy, window->drawable, window->gc, | |
830 window->w - t->scrollbar_width, 0, t->scrollbar_widt… | |
831 ); | |
832 XSetForeground(window->common->dpy, window->gc, t->scrol… | |
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_c… | |
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,… | |
860 ); | |
861 /* FIXME: Is this the best position? The bottom of the l… | |
862 just the bottom of the window, so an input method box… | |
863 have to be moved out of the way anyways (fcitx just m… | |
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_… | |
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->… | |
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_… | |
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… | |
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 || elapse… | |
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 * PAN… | |
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-… | |
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… | |
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 + scrol… | |
1030 double new_scroll_y = y - scroll… | |
1031 set_scroll_pos(window, new_scrol… | |
1032 } | |
1033 window->redraw = 1; | |
1034 } else if (y < window->text_h) { | |
1035 if (window->button_callback) | |
1036 window->button_callback(window->… | |
1037 window->redraw = 1; | |
1038 } | |
1039 break; | |
1040 case Button2: | |
1041 if (x < window->text_w && y < window->text_h && … | |
1042 window->button_callback(window->button_c… | |
1043 break; | |
1044 case Button4: | |
1045 case Button5: | |
1046 window->scroll_offset += scroll_num * theme->scr… | |
1047 if (window->scroll_offset < 0) | |
1048 window->scroll_offset = 0; | |
1049 if (window->scroll_offset + window->text_h > win… | |
1050 window->scroll_offset = window->scroll_m… | |
1051 } | |
1052 if (window->scroll_callback) | |
1053 window->scroll_callback(window->scroll_c… | |
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, … | |
1067 window->redraw = 1; | |
1068 } else if (event->xbutton.button == Button2 && in_text && window… | |
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_handl… | |
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, … | |
1085 window->redraw = 1; | |
1086 } | |
1087 } |