From: Michael D. Lowis Date: Wed, 2 Jan 2019 03:06:50 +0000 (-0500) Subject: remove win.c and move its code to tide.c since it is not actually shared X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=86a82f329f6245439db99c69703e0f17bff6644b;p=projs%2Ftide.git remove win.c and move its code to tide.c since it is not actually shared --- diff --git a/Makefile b/Makefile index 1836afa..324fab1 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,6 @@ LIBEDIT_OBJS = \ src/lib/utf8.o \ src/lib/job.o \ src/lib/view.o \ - src/lib/win.o \ src/lib/x11.o \ src/lib/x11_gc.o \ src/lib/x11_sel.o diff --git a/TODO.md b/TODO.md index c8c7516..1bcc5be 100644 --- a/TODO.md +++ b/TODO.md @@ -20,7 +20,6 @@ ## BACKLOG -* move mouse handlers back into tide.c * Add matching logic for "", '', ``, and <> * B2+B1 click executes command with selection as argument * implement script for toggling between header and source file diff --git a/src/lib/win.c b/src/lib/win.c deleted file mode 100644 index 131fb3d..0000000 --- a/src/lib/win.c +++ /dev/null @@ -1,486 +0,0 @@ -#define _POSIX_C_SOURCE 200809L -#define _XOPEN_SOURCE 700 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -/******************************************************************************/ -static Time Now; -static struct XConf X; -static int KeyBtnState; -static WinRegion Focused = EDIT; -static View Regions[NREGIONS]; -static KeyBinding* Keys = NULL; -static int Divider; -static int FontSel; -static bool SyncMouse = false; - -int SearchDir = DOWN; -char* SearchTerm = NULL; - -/******************************************************************************/ - -#define PRESSED(btn) \ - ((KeyBtnState & (1 << (btn + 7))) == (1 << (btn + 7))) - -static void font_load(char* name) { - XftFont* font = x11_font_load(&X, name); - X.font = (font ? font : X.font); // Update if we found a new font -} - -static void get_position(WinRegion id, int x, int y, size_t* row, size_t* col) { - int starty = (id == EDIT ? Divider+3 : 0); - int startx = ScrollWidth+3; - int maxy = (id == EDIT - ? (Divider + (int)(win_view(EDIT)->nrows * X.font->height)) - : Divider - 4 - ); - x = (x < 0 ? 0 : (x > X.width ? X.width : x)); - y = (y < starty ? starty : (y > maxy ? maxy : y)); - *row = (y - starty) / X.font->height; - *col = (startx <= x ? x - startx : 0); -} - -static void tide_send(char* type) { - XEvent ev; - memset(&ev, 0, sizeof (ev)); - Atom xa_registrar = XInternAtom(X.display, "TIDE_REGISTRAR", 0); - Window regwin = XGetSelectionOwner(X.display, xa_registrar); - if (None != regwin) { - ev.xclient.type = ClientMessage; - ev.xclient.window = X.self; - ev.xclient.message_type = xa_registrar; - ev.xclient.format = 32; - ev.xclient.data.l[0] = XInternAtom(X.display, type, 0); - XSendEvent(X.display, regwin, False, NoEventMask, &ev); - XFlush(X.display); - } -} - -/******************************************************************************/ - -static uint32_t special_keys(uint32_t key) { - static uint32_t keymap[256] = { - /* Function keys */ - [0xBE] = KEY_F1, [0xBF] = KEY_F2, [0xC0] = KEY_F3, [0xC1] = KEY_F4, - [0xC2] = KEY_F5, [0xC3] = KEY_F6, [0xC4] = KEY_F7, [0xC5] = KEY_F8, - [0xC6] = KEY_F9, [0xC7] = KEY_F10, [0xC8] = KEY_F11, [0xC9] = KEY_F12, - /* Navigation keys */ - [0x50] = KEY_HOME, [0x51] = KEY_LEFT, [0x52] = KEY_UP, - [0x53] = KEY_RIGHT, [0x54] = KEY_DOWN, [0x55] = KEY_PGUP, - [0x56] = KEY_PGDN, [0x57] = KEY_END, - /* Control keys */ - [0x08] = '\b', [0x09] = '\t', [0x0d] = '\n', [0x0a] = '\n', - /* Miscellaneous */ - [0x63] = KEY_INSERT, [0x1B] = KEY_ESCAPE, [0xFF] = KEY_DELETE, - }; - /* lookup the key by keysym */ - key = ((key & 0xFF00) == 0xFF00 ? keymap[key & 0xFF] : key); - return (!key ? RUNE_ERR : key); -} - -static uint32_t getkey(XConf* x, XEvent* e) { - KeySym key = x11_getkey(x, e); - /* if it's ascii, just return it */ - if (key >= 0x20 && key <= 0x7F) - return (uint32_t)key; - /* translate special key codes into unicode codepoints */ - return special_keys(key); -} - -static void mouse_left(WinRegion id, bool pressed, size_t row, size_t col) { - static int count = 0; - static Time before = 0; - if (!pressed) return; - count = ((Now - before) <= (uint64_t)ClickTime ? count+1 : 1); - before = Now; - if (PRESSED(MouseRight)) { - puts("fetch tag"); - } else if (PRESSED(MouseMiddle)) { - puts("exec with arg"); - } else { - if (count == 1) - view_setcursor(win_view(id), row, col, win_keymodsset(ModShift)); - else if (count == 2) - view_select(win_view(id), row, col); - else if (count == 3) - view_selword(win_view(id), row, col); - } -} - -static void mouse_middle(WinRegion id, bool pressed, size_t row, size_t col) { - if (pressed) return; - if (PRESSED(MouseLeft)) { - cut(NULL); - } else { - char* str = view_fetch(win_view(id), row, col, riscmd); - if (str) exec(str); - free(str); - } -} - -static void mouse_right(WinRegion id, bool pressed, size_t row, size_t col) { - if (pressed) return; - if (PRESSED(MouseLeft)) { - paste(NULL); - } else { - FetchCmd[1] = view_fetch(win_view(id), row, col, risfile); - if (job_run(FetchCmd) != 0) { - SearchDir *= (win_keymodsset(ModShift) ? -1 : +1); - free(SearchTerm); - SearchTerm = view_fetch(win_view(id), row, col, risfile); - view_findstr(win_view(EDIT), SearchDir, SearchTerm); - SyncMouse = true; - } else { - free(FetchCmd[1]); - } - } -} - -static void mouse_scroll(WinRegion id, bool pressed, int amount) { - if (!pressed) - view_scroll(win_view(id), amount); -} - -static void mouse_click(int btn, bool pressed, int x, int y) { - size_t row, col; - Focused = (y <= Divider ? TAGS : EDIT); - get_position(Focused, x, y, &row, &col); - switch(btn) { - case MouseLeft: mouse_left(Focused, pressed, row, col); break; - case MouseMiddle: mouse_middle(Focused, pressed, row, col); break; - case MouseRight: mouse_right(Focused, pressed, row, col); break; - case MouseWheelUp: mouse_scroll(Focused, pressed, -ScrollBy); break; - case MouseWheelDn: mouse_scroll(Focused, pressed, +ScrollBy); break; - } -} - -/******************************************************************************/ - -size_t glyph_width(View* view, int c) { - FcChar32 rune = (FcChar32)c; - XGlyphInfo extents; - XftFont* font = (&Regions[TAGS] == view ? X.tagfont : X.font); - XftTextExtents32(X.display, font, &rune, 1, &extents); - if (c == '\t') - return (TabWidth * extents.xOff); - else - return extents.xOff; -} - -static void draw_rect(int color, int x, int y, int width, int height) { - x11_draw_rect(&X, Palette[color], x, y, width, height); -} - -static void draw_statbox(void) { - draw_rect(VerBdr, ScrollWidth, 0, 1, X.height/4); - switch (win_view(EDIT)->buffer.status) { - case NORMAL: draw_rect(TagsBg, 0, 0, ScrollWidth, X.height/4); break; - case MODIFIED: draw_rect(Purple, 0, 0, ScrollWidth, X.height/4); break; - case OUTDATED: draw_rect(Orange, 0, 0, ScrollWidth, X.height/4); break; - case ERRORED: draw_rect(Red, 0, 0, ScrollWidth, X.height/4); break; - } -} - -static bool draw_csr(View* view, size_t fheight, int x, int y, bool csrdrawn) { - if (!csrdrawn && !view_selsize(view)) { - int csrclr = (view == &Regions[TAGS] ? TagsCsr : EditCsr); - draw_rect(csrclr, x-1, y, 3, 3); - draw_rect(csrclr, x, y, 1, fheight); - draw_rect(csrclr, x-1, y+fheight-3, 3, 3); - csrdrawn = true; - } - if (SyncMouse && view == &Regions[EDIT]) { - XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, x-4, y + fheight/2); - SyncMouse = false; - } - return csrdrawn; -} - -static void draw_view(View* view, XftFont* font, size_t nrows, drawcsr* csr, int bg, int fg, int sel) { - int nspecs = 0; - XftGlyphSpec* specs = NULL; - size_t fheight = font->height; - bool csr_drawn = false; - /* draw the view to the window */ - view_resize(view, (csr->w - csr->x), nrows); - view_update(view); - draw_rect(bg, csr->x, csr->y, csr->w, ((nrows + 1) * fheight) + 9); - for (size_t i = 0; i < nrows; i++) { - Row* row = view_getrow(view, i + view->index); - size_t x = (csr->x + 2), y = (csr->y + 2 + (i * fheight)); - for (size_t i = 0; i < row->len; i++) { - int rune = row->cols[i].rune; - if (rune == '\r' || rune == '\n' || rune == '\t') - rune = ' '; - if (buf_insel(&(view->buffer), row->cols[i].off)) - draw_rect(sel, x, y, row->cols[i].width, fheight); - if (row->cols[i].off == view->buffer.selection.end) - csr_drawn = draw_csr(view, fheight, x, y, csr_drawn); - specs = realloc(specs, sizeof(XftGlyphSpec) * ++nspecs); - specs[nspecs-1].glyph = XftCharIndex(X.display, font, rune); - specs[nspecs-1].x = x; - specs[nspecs-1].y = y + font->ascent; - x += row->cols[i].width; - } - } - x11_draw_glyphs(&X, Palette[fg], font, specs, nspecs); - csr->y += (nrows * fheight) + 3; - free(specs); -} - -static void draw_hrule(drawcsr* csr) { - draw_rect(HorBdr, 0, csr->y + 1, X.width, 1); - Divider = csr->y; - csr->y += 2; -} - -static void draw_scroll(drawcsr* csr) { - View* view = win_view(EDIT); - size_t bend = buf_end(win_buf(EDIT)); - if (bend == 0) bend = 1; - if (!view->rows || !view->nrows) return; - size_t vbeg = view->rows[0]->off; - size_t vend = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->len; - double scroll_vis = (double)(vend - vbeg) / (double)bend; - double scroll_off = ((double)vbeg / (double)bend); - size_t thumbreg = (csr->h - Divider) + 4; - size_t thumboff = (size_t)((thumbreg * scroll_off) + Divider); - size_t thumbsz = (size_t)(thumbreg * scroll_vis); - if (thumbsz < 5) thumbsz = 5; - draw_rect(VerBdr, ScrollWidth, Divider + 2, 1, thumbreg); - draw_rect(ScrollBg, 0, Divider + 2, ScrollWidth, thumbreg); - draw_rect(ScrollFg, 0, thumboff + 2, ScrollWidth, thumbsz); -} - -/******************************************************************************/ - -static void xkeypress(XConf* x, XEvent* e) { - (void)x; - Now = e->xkey.time; - Focused = (e->xkey.y <= Divider ? TAGS : EDIT); - uint32_t key = getkey(x, e); - if (key == RUNE_ERR) return; - KeyBtnState = e->xkey.state; - int mods = KeyBtnState & (ModCtrl|ModShift|ModAlt); - int32_t mkey = tolower(key); - for (KeyBinding* bind = Keys; bind && bind->key; bind++) { - bool match = (mkey == bind->key); - bool exact = (bind->mods == mods); - bool any = (bind->mods == ModAny); - bool oneplus = ((bind->mods == ModOneOrMore) && (mods & ModOneOrMore)); - if (match && (exact || oneplus || any)) { - bind->fn(bind->arg); - return; - } - } - /* fallback to just inserting the rune if it doesn't fall in the private use area. - * the private use area is used to encode special keys */ - if (key < 0xE000 || key > 0xF8FF) - view_insert(win_view(FOCUSED), key); -} - -static void xbtnpress(XConf* x, XEvent* e) { - (void)x; - Now = e->xbutton.time; - KeyBtnState = (e->xbutton.state | (1 << (e->xbutton.button + 7))); - mouse_click(e->xbutton.button, true, e->xbutton.x, e->xbutton.y); -} - -static void xbtnrelease(XConf* x, XEvent* e) { - (void)x; - Now = e->xbutton.time; - KeyBtnState = (e->xbutton.state & ~(1 << (e->xbutton.button + 7))); - mouse_click(e->xbutton.button, false, e->xbutton.x, e->xbutton.y); -} - -static void xbtnmotion(XConf* x, XEvent* e) { - while (XCheckTypedEvent(x->display, MotionNotify, e)); - Now = e->xbutton.time; - size_t row, col; - KeyBtnState = e->xbutton.state; - int xpos = e->xbutton.x, ypos = e->xbutton.y; - get_position(Focused, xpos, ypos, &row, &col); - if (PRESSED(MouseLeft)) - view_setcursor(win_view(Focused), row, col, true); -} - -static void xclientmsg(XConf* x, XEvent* e) { - if ((Atom)(e->xclient.data.l[0]) == XInternAtom(x->display, "WM_DELETE_WINDOW", False)) - win_quit(); - else if (e->xclient.message_type == XInternAtom(x->display, "GOTO", False)) - view_setln(win_view(EDIT), e->xclient.data.l[0]); -} - -static void xupdate(Job* job) { - int nevents, nqueued; - do { - nqueued = XEventsQueued(X.display, QueuedAfterFlush); - XGetMotionEvents(X.display, X.self, CurrentTime, CurrentTime, &nevents); - for (XEvent e; XPending(X.display);) { - XNextEvent(X.display, &e); - if (!XFilterEvent(&e, None) && X.eventfns[e.type]) - (X.eventfns[e.type])(&X, &e); - for (int status; waitpid(-1, &status, WNOHANG) > 0;); - } - if (nqueued || !job) { - /* force update the title */ - win_title(NULL); - /* determine the size of the regions */ - size_t maxtagrows = ((X.height/4) / X.tagfont->height); - size_t tagrows = view_limitrows(win_view(TAGS), maxtagrows); - size_t tagregsz = (tagrows * X.tagfont->height) + 7; - size_t editrows = (X.height - tagregsz) / X.font->height ; - /* draw the regions to the window */ - drawcsr csr = { .w = X.width, .h = X.height }; - csr.x += ScrollWidth + 1; - draw_statbox(); - draw_view(&Regions[TAGS], X.tagfont, tagrows, &csr, TagsBg, TagsFg, TagsSel); - draw_hrule(&csr); - draw_view(&Regions[EDIT], X.font, editrows, &csr, EditBg, EditFg, EditSel); - draw_scroll(&csr); - XCopyArea(X.display, X.pixmap, X.self, X.gc, 0, 0, X.width, X.height, 0, 0); - } - } while ((nqueued = XEventsQueued(X.display, QueuedAfterFlush)) > 0); -} - -/******************************************************************************/ - -void win_init(KeyBinding* bindings) { - Keys = bindings; - view_init(&Regions[TAGS], NULL); - view_init(&Regions[EDIT], NULL); - signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal - setlocale(LC_CTYPE, ""); - XSetLocaleModifiers(""); - - /* open the X display and get basic attributes */ - x11_init(&X); - font_load(Fonts[FontSel = 0]); - X.tagfont = X.font; - if (!X.font) { - perror("unable to load base font"); - exit(EXIT_FAILURE); - } - x11_mkwin(&X, 640, 480, 0 - | FocusChangeMask - | KeyPressMask - | ButtonPressMask - | ButtonReleaseMask - | ButtonMotionMask - | StructureNotifyMask - | PropertyChangeMask - | ExposureMask - ); - x11_init_gc(&X); - x11_sel_init(&X); - x11_centerwin(&X); - x11_show(&X); - - /* register event handlers */ - X.eventfns[KeyPress] = xkeypress; - X.eventfns[ButtonPress] = xbtnpress; - X.eventfns[ButtonRelease] = xbtnrelease; - X.eventfns[MotionNotify] = xbtnmotion; - X.eventfns[ClientMessage] = xclientmsg; - - /* Populate the tags region */ - View* view = win_view(TAGS); - view_putstr(view, TagString); - buf_logclear(&(view->buffer)); -} - -void win_title(char* path) { - static char prevtitle[4096] = {0}; - char title[4096] = {0}; - if (!path) path = win_view(EDIT)->buffer.path; - if (!path) path = "*scratch*"; - snprintf(title, sizeof(title)-1, "[%c%c] %s", - (DosLineFeed ? 'C' : 'N'), - (ExpandTabs ? 'S' : 'T'), - path); - if (strcmp(prevtitle, title)) - XStoreName(X.display, X.self, title); - memcpy(prevtitle, title, sizeof(title)); -} - -void win_font(char* font) { - font_load(font ? font : Fonts[++FontSel % nelem(Fonts)]); -} - -void win_prop_set(char* xname, char* ename, char* value) { - if (!value) return; - Atom propname = XInternAtom(X.display, xname, 0); - XChangeProperty(X.display, X.self, propname, XA_STRING, 8, PropModeReplace, - (const unsigned char *)value, strlen(value)); - if (ename) setenv(ename, value, 1); -} - -void win_update(int ms) { - if (job_poll(ms)) - xupdate(NULL); -} - -void win_loop(void) { - X.running = True; - XMapWindow(X.display, X.self); - tide_send("ADD"); - job_spawn(ConnectionNumber(X.display), xupdate, 0, 0); - while (X.running) { - win_update(Timeout); - } -} - -void win_quit(void) { - static uint64_t before = 0; - if ((win_buf(EDIT)->status != MODIFIED) || (Now - before) <= (uint64_t)ClickTime) { - tide_send("DEL"); - X.eventfns[SelectionClear] = x11_sel_quit; - XUnmapWindow(X.display, X.self); - if (!x11_sel_ready(&X)) { - X.running = False; - } else { - if (fork()) exit(0); /* fork into background if we still have selection */ - } - } - before = Now; -} - -void win_togglefocus(void) { - int ypos = (Focused == EDIT ? Divider/2 : (X.height - ((X.height-Divider) / 2))); - XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, X.width/2, ypos); -} - -View* win_view(WinRegion id) { - return &(Regions[id == FOCUSED ? Focused : id]); -} - -Buf* win_buf(WinRegion id) { - return &(Regions[id == FOCUSED ? Focused : id].buffer); -} - -bool win_keymodsset(int mask) { - return ((KeyBtnState & mask) == mask); -} - -bool win_sel_get(int selid, void(*cbfn)(char*)) { - return x11_sel_get(&X, selid, cbfn); -} - -bool win_sel_set(int selid, char* str) { - return x11_sel_set(&X, selid, str); -} - -void win_syncmouse(void) { - SyncMouse = true; -} diff --git a/src/tide.c b/src/tide.c index ca99bf2..47fa00b 100644 --- a/src/tide.c +++ b/src/tide.c @@ -1,13 +1,19 @@ +//#include +//#include +//#include +//#include +//#include + +#define _POSIX_C_SOURCE 200809L #define _XOPEN_SOURCE 700 #include #include #include #include +#include #include #include - -#define PRIMARY 0 -#define CLIPBOARD 1 +#include #define INCLUDE_DEFS #include "config.h" @@ -19,6 +25,466 @@ typedef struct { char* ARGV0; static Tag Builtins[16]; +static Time Now; +static struct XConf X; +static int KeyBtnState; +static WinRegion Focused = EDIT; +static View Regions[NREGIONS]; +static KeyBinding* Keys = NULL; +static int Divider; +static int FontSel; +static bool SyncMouse = false; +int SearchDir = DOWN; +char* SearchTerm = NULL; + +#define PRESSED(btn) \ + ((KeyBtnState & (1 << (btn + 7))) == (1 << (btn + 7))) + +/* X11 Window Code + ******************************************************************************/ +static void font_load(char* name) { + XftFont* font = x11_font_load(&X, name); + X.font = (font ? font : X.font); // Update if we found a new font +} + +static void get_position(WinRegion id, int x, int y, size_t* row, size_t* col) { + int starty = (id == EDIT ? Divider+3 : 0); + int startx = ScrollWidth+3; + int maxy = (id == EDIT + ? (Divider + (int)(win_view(EDIT)->nrows * X.font->height)) + : Divider - 4 + ); + x = (x < 0 ? 0 : (x > X.width ? X.width : x)); + y = (y < starty ? starty : (y > maxy ? maxy : y)); + *row = (y - starty) / X.font->height; + *col = (startx <= x ? x - startx : 0); +} + +static void tide_send(char* type) { + XEvent ev; + memset(&ev, 0, sizeof (ev)); + Atom xa_registrar = XInternAtom(X.display, "TIDE_REGISTRAR", 0); + Window regwin = XGetSelectionOwner(X.display, xa_registrar); + if (None != regwin) { + ev.xclient.type = ClientMessage; + ev.xclient.window = X.self; + ev.xclient.message_type = xa_registrar; + ev.xclient.format = 32; + ev.xclient.data.l[0] = XInternAtom(X.display, type, 0); + XSendEvent(X.display, regwin, False, NoEventMask, &ev); + XFlush(X.display); + } +} + +static uint32_t special_keys(uint32_t key) { + static uint32_t keymap[256] = { + /* Function keys */ + [0xBE] = KEY_F1, [0xBF] = KEY_F2, [0xC0] = KEY_F3, [0xC1] = KEY_F4, + [0xC2] = KEY_F5, [0xC3] = KEY_F6, [0xC4] = KEY_F7, [0xC5] = KEY_F8, + [0xC6] = KEY_F9, [0xC7] = KEY_F10, [0xC8] = KEY_F11, [0xC9] = KEY_F12, + /* Navigation keys */ + [0x50] = KEY_HOME, [0x51] = KEY_LEFT, [0x52] = KEY_UP, + [0x53] = KEY_RIGHT, [0x54] = KEY_DOWN, [0x55] = KEY_PGUP, + [0x56] = KEY_PGDN, [0x57] = KEY_END, + /* Control keys */ + [0x08] = '\b', [0x09] = '\t', [0x0d] = '\n', [0x0a] = '\n', + /* Miscellaneous */ + [0x63] = KEY_INSERT, [0x1B] = KEY_ESCAPE, [0xFF] = KEY_DELETE, + }; + /* lookup the key by keysym */ + key = ((key & 0xFF00) == 0xFF00 ? keymap[key & 0xFF] : key); + return (!key ? RUNE_ERR : key); +} + +static uint32_t getkey(XConf* x, XEvent* e) { + KeySym key = x11_getkey(x, e); + /* if it's ascii, just return it */ + if (key >= 0x20 && key <= 0x7F) + return (uint32_t)key; + /* translate special key codes into unicode codepoints */ + return special_keys(key); +} + +static void mouse_left(WinRegion id, bool pressed, size_t row, size_t col) { + static int count = 0; + static Time before = 0; + if (!pressed) return; + count = ((Now - before) <= (uint64_t)ClickTime ? count+1 : 1); + before = Now; + if (PRESSED(MouseRight)) { + puts("fetch tag"); + } else if (PRESSED(MouseMiddle)) { + puts("exec with arg"); + } else { + if (count == 1) + view_setcursor(win_view(id), row, col, win_keymodsset(ModShift)); + else if (count == 2) + view_select(win_view(id), row, col); + else if (count == 3) + view_selword(win_view(id), row, col); + } +} + +static void mouse_middle(WinRegion id, bool pressed, size_t row, size_t col) { + if (pressed) return; + if (PRESSED(MouseLeft)) { + cut(NULL); + } else { + char* str = view_fetch(win_view(id), row, col, riscmd); + if (str) exec(str); + free(str); + } +} + +static void mouse_right(WinRegion id, bool pressed, size_t row, size_t col) { + if (pressed) return; + if (PRESSED(MouseLeft)) { + paste(NULL); + } else { + FetchCmd[1] = view_fetch(win_view(id), row, col, risfile); + if (job_run(FetchCmd) != 0) { + SearchDir *= (win_keymodsset(ModShift) ? -1 : +1); + free(SearchTerm); + SearchTerm = view_fetch(win_view(id), row, col, risfile); + view_findstr(win_view(EDIT), SearchDir, SearchTerm); + SyncMouse = true; + } else { + free(FetchCmd[1]); + } + } +} + +static void mouse_scroll(WinRegion id, bool pressed, int amount) { + if (!pressed) + view_scroll(win_view(id), amount); +} + +static void mouse_click(int btn, bool pressed, int x, int y) { + size_t row, col; + Focused = (y <= Divider ? TAGS : EDIT); + get_position(Focused, x, y, &row, &col); + switch(btn) { + case MouseLeft: mouse_left(Focused, pressed, row, col); break; + case MouseMiddle: mouse_middle(Focused, pressed, row, col); break; + case MouseRight: mouse_right(Focused, pressed, row, col); break; + case MouseWheelUp: mouse_scroll(Focused, pressed, -ScrollBy); break; + case MouseWheelDn: mouse_scroll(Focused, pressed, +ScrollBy); break; + } +} + +size_t glyph_width(View* view, int c) { + FcChar32 rune = (FcChar32)c; + XGlyphInfo extents; + XftFont* font = (&Regions[TAGS] == view ? X.tagfont : X.font); + XftTextExtents32(X.display, font, &rune, 1, &extents); + if (c == '\t') + return (TabWidth * extents.xOff); + else + return extents.xOff; +} + +static void draw_rect(int color, int x, int y, int width, int height) { + x11_draw_rect(&X, Palette[color], x, y, width, height); +} + +static void draw_statbox(void) { + draw_rect(VerBdr, ScrollWidth, 0, 1, X.height/4); + switch (win_view(EDIT)->buffer.status) { + case NORMAL: draw_rect(TagsBg, 0, 0, ScrollWidth, X.height/4); break; + case MODIFIED: draw_rect(Purple, 0, 0, ScrollWidth, X.height/4); break; + case OUTDATED: draw_rect(Orange, 0, 0, ScrollWidth, X.height/4); break; + case ERRORED: draw_rect(Red, 0, 0, ScrollWidth, X.height/4); break; + } +} + +static bool draw_csr(View* view, size_t fheight, int x, int y, bool csrdrawn) { + if (!csrdrawn && !view_selsize(view)) { + int csrclr = (view == &Regions[TAGS] ? TagsCsr : EditCsr); + draw_rect(csrclr, x-1, y, 3, 3); + draw_rect(csrclr, x, y, 1, fheight); + draw_rect(csrclr, x-1, y+fheight-3, 3, 3); + csrdrawn = true; + } + if (SyncMouse && view == &Regions[EDIT]) { + XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, x-4, y + fheight/2); + SyncMouse = false; + } + return csrdrawn; +} + +static void draw_view(View* view, XftFont* font, size_t nrows, drawcsr* csr, int bg, int fg, int sel) { + int nspecs = 0; + XftGlyphSpec* specs = NULL; + size_t fheight = font->height; + bool csr_drawn = false; + /* draw the view to the window */ + view_resize(view, (csr->w - csr->x), nrows); + view_update(view); + draw_rect(bg, csr->x, csr->y, csr->w, ((nrows + 1) * fheight) + 9); + for (size_t i = 0; i < nrows; i++) { + Row* row = view_getrow(view, i + view->index); + size_t x = (csr->x + 2), y = (csr->y + 2 + (i * fheight)); + for (size_t i = 0; i < row->len; i++) { + int rune = row->cols[i].rune; + if (rune == '\r' || rune == '\n' || rune == '\t') + rune = ' '; + if (buf_insel(&(view->buffer), row->cols[i].off)) + draw_rect(sel, x, y, row->cols[i].width, fheight); + if (row->cols[i].off == view->buffer.selection.end) + csr_drawn = draw_csr(view, fheight, x, y, csr_drawn); + specs = realloc(specs, sizeof(XftGlyphSpec) * ++nspecs); + specs[nspecs-1].glyph = XftCharIndex(X.display, font, rune); + specs[nspecs-1].x = x; + specs[nspecs-1].y = y + font->ascent; + x += row->cols[i].width; + } + } + x11_draw_glyphs(&X, Palette[fg], font, specs, nspecs); + csr->y += (nrows * fheight) + 3; + free(specs); +} + +static void draw_hrule(drawcsr* csr) { + draw_rect(HorBdr, 0, csr->y + 1, X.width, 1); + Divider = csr->y; + csr->y += 2; +} + +static void draw_scroll(drawcsr* csr) { + View* view = win_view(EDIT); + size_t bend = buf_end(win_buf(EDIT)); + if (bend == 0) bend = 1; + if (!view->rows || !view->nrows) return; + size_t vbeg = view->rows[0]->off; + size_t vend = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->len; + double scroll_vis = (double)(vend - vbeg) / (double)bend; + double scroll_off = ((double)vbeg / (double)bend); + size_t thumbreg = (csr->h - Divider) + 4; + size_t thumboff = (size_t)((thumbreg * scroll_off) + Divider); + size_t thumbsz = (size_t)(thumbreg * scroll_vis); + if (thumbsz < 5) thumbsz = 5; + draw_rect(VerBdr, ScrollWidth, Divider + 2, 1, thumbreg); + draw_rect(ScrollBg, 0, Divider + 2, ScrollWidth, thumbreg); + draw_rect(ScrollFg, 0, thumboff + 2, ScrollWidth, thumbsz); +} + +static void xkeypress(XConf* x, XEvent* e) { + (void)x; + Now = e->xkey.time; + Focused = (e->xkey.y <= Divider ? TAGS : EDIT); + uint32_t key = getkey(x, e); + if (key == RUNE_ERR) return; + KeyBtnState = e->xkey.state; + int mods = KeyBtnState & (ModCtrl|ModShift|ModAlt); + int32_t mkey = tolower(key); + for (KeyBinding* bind = Keys; bind && bind->key; bind++) { + bool match = (mkey == bind->key); + bool exact = (bind->mods == mods); + bool any = (bind->mods == ModAny); + bool oneplus = ((bind->mods == ModOneOrMore) && (mods & ModOneOrMore)); + if (match && (exact || oneplus || any)) { + bind->fn(bind->arg); + return; + } + } + /* fallback to just inserting the rune if it doesn't fall in the private use area. + * the private use area is used to encode special keys */ + if (key < 0xE000 || key > 0xF8FF) + view_insert(win_view(FOCUSED), key); +} + +static void xbtnpress(XConf* x, XEvent* e) { + (void)x; + Now = e->xbutton.time; + KeyBtnState = (e->xbutton.state | (1 << (e->xbutton.button + 7))); + mouse_click(e->xbutton.button, true, e->xbutton.x, e->xbutton.y); +} + +static void xbtnrelease(XConf* x, XEvent* e) { + (void)x; + Now = e->xbutton.time; + KeyBtnState = (e->xbutton.state & ~(1 << (e->xbutton.button + 7))); + mouse_click(e->xbutton.button, false, e->xbutton.x, e->xbutton.y); +} + +static void xbtnmotion(XConf* x, XEvent* e) { + while (XCheckTypedEvent(x->display, MotionNotify, e)); + Now = e->xbutton.time; + size_t row, col; + KeyBtnState = e->xbutton.state; + int xpos = e->xbutton.x, ypos = e->xbutton.y; + get_position(Focused, xpos, ypos, &row, &col); + if (PRESSED(MouseLeft)) + view_setcursor(win_view(Focused), row, col, true); +} + +static void xclientmsg(XConf* x, XEvent* e) { + if ((Atom)(e->xclient.data.l[0]) == XInternAtom(x->display, "WM_DELETE_WINDOW", False)) + win_quit(); + else if (e->xclient.message_type == XInternAtom(x->display, "GOTO", False)) + view_setln(win_view(EDIT), e->xclient.data.l[0]); +} + +static void xupdate(Job* job) { + int nevents, nqueued; + do { + nqueued = XEventsQueued(X.display, QueuedAfterFlush); + XGetMotionEvents(X.display, X.self, CurrentTime, CurrentTime, &nevents); + for (XEvent e; XPending(X.display);) { + XNextEvent(X.display, &e); + if (!XFilterEvent(&e, None) && X.eventfns[e.type]) + (X.eventfns[e.type])(&X, &e); + for (int status; waitpid(-1, &status, WNOHANG) > 0;); + } + if (nqueued || !job) { + /* force update the title */ + win_title(NULL); + /* determine the size of the regions */ + size_t maxtagrows = ((X.height/4) / X.tagfont->height); + size_t tagrows = view_limitrows(win_view(TAGS), maxtagrows); + size_t tagregsz = (tagrows * X.tagfont->height) + 7; + size_t editrows = (X.height - tagregsz) / X.font->height ; + /* draw the regions to the window */ + drawcsr csr = { .w = X.width, .h = X.height }; + csr.x += ScrollWidth + 1; + draw_statbox(); + draw_view(&Regions[TAGS], X.tagfont, tagrows, &csr, TagsBg, TagsFg, TagsSel); + draw_hrule(&csr); + draw_view(&Regions[EDIT], X.font, editrows, &csr, EditBg, EditFg, EditSel); + draw_scroll(&csr); + XCopyArea(X.display, X.pixmap, X.self, X.gc, 0, 0, X.width, X.height, 0, 0); + } + } while ((nqueued = XEventsQueued(X.display, QueuedAfterFlush)) > 0); +} + +void win_init(KeyBinding* bindings) { + Keys = bindings; + view_init(&Regions[TAGS], NULL); + view_init(&Regions[EDIT], NULL); + signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + + /* open the X display and get basic attributes */ + x11_init(&X); + font_load(Fonts[FontSel = 0]); + X.tagfont = X.font; + if (!X.font) { + perror("unable to load base font"); + exit(EXIT_FAILURE); + } + x11_mkwin(&X, 640, 480, 0 + | FocusChangeMask + | KeyPressMask + | ButtonPressMask + | ButtonReleaseMask + | ButtonMotionMask + | StructureNotifyMask + | PropertyChangeMask + | ExposureMask + ); + x11_init_gc(&X); + x11_sel_init(&X); + x11_centerwin(&X); + x11_show(&X); + + /* register event handlers */ + X.eventfns[KeyPress] = xkeypress; + X.eventfns[ButtonPress] = xbtnpress; + X.eventfns[ButtonRelease] = xbtnrelease; + X.eventfns[MotionNotify] = xbtnmotion; + X.eventfns[ClientMessage] = xclientmsg; + + /* Populate the tags region */ + View* view = win_view(TAGS); + view_putstr(view, TagString); + buf_logclear(&(view->buffer)); +} + +void win_title(char* path) { + static char prevtitle[4096] = {0}; + char title[4096] = {0}; + if (!path) path = win_view(EDIT)->buffer.path; + if (!path) path = "*scratch*"; + snprintf(title, sizeof(title)-1, "[%c%c] %s", + (DosLineFeed ? 'C' : 'N'), + (ExpandTabs ? 'S' : 'T'), + path); + if (strcmp(prevtitle, title)) + XStoreName(X.display, X.self, title); + memcpy(prevtitle, title, sizeof(title)); +} + +void win_font(char* font) { + font_load(font ? font : Fonts[++FontSel % nelem(Fonts)]); +} + +void win_prop_set(char* xname, char* ename, char* value) { + if (!value) return; + Atom propname = XInternAtom(X.display, xname, 0); + XChangeProperty(X.display, X.self, propname, XA_STRING, 8, PropModeReplace, + (const unsigned char *)value, strlen(value)); + if (ename) setenv(ename, value, 1); +} + +void win_update(int ms) { + if (job_poll(ms)) + xupdate(NULL); +} + +void win_loop(void) { + X.running = True; + XMapWindow(X.display, X.self); + tide_send("ADD"); + job_spawn(ConnectionNumber(X.display), xupdate, 0, 0); + while (X.running) { + win_update(Timeout); + } +} + +void win_quit(void) { + static uint64_t before = 0; + if ((win_buf(EDIT)->status != MODIFIED) || (Now - before) <= (uint64_t)ClickTime) { + tide_send("DEL"); + X.eventfns[SelectionClear] = x11_sel_quit; + XUnmapWindow(X.display, X.self); + if (!x11_sel_ready(&X)) { + X.running = False; + } else { + if (fork()) exit(0); /* fork into background if we still have selection */ + } + } + before = Now; +} + +void win_togglefocus(void) { + int ypos = (Focused == EDIT ? Divider/2 : (X.height - ((X.height-Divider) / 2))); + XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, X.width/2, ypos); +} + +View* win_view(WinRegion id) { + return &(Regions[id == FOCUSED ? Focused : id]); +} + +Buf* win_buf(WinRegion id) { + return &(Regions[id == FOCUSED ? Focused : id].buffer); +} + +bool win_keymodsset(int mask) { + return ((KeyBtnState & mask) == mask); +} + +bool win_sel_get(int selid, void(*cbfn)(char*)) { + return x11_sel_get(&X, selid, cbfn); +} + +bool win_sel_set(int selid, char* str) { + return x11_sel_set(&X, selid, str); +} + +void win_syncmouse(void) { + SyncMouse = true; +} /* Tag/Cmd Execution ******************************************************************************/