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 }