URI: 
       window.c - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }