From: NeilBrown Date: Sat, 30 Jan 2016 21:25:47 +0000 (+1100) Subject: devel X-Git-Tag: lca2016~3 X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=32e44ce6a70afd4f70ee78fe38a7c2354ef2bb5d;p=edlib.git devel --- diff --git a/core-doc.c b/core-doc.c index ff068f74..5b046176 100644 --- a/core-doc.c +++ b/core-doc.c @@ -494,8 +494,12 @@ DEF_CMD(doc_handle) if (strcmp(ci->key, "doc:vmark-get") == 0) { struct mark *m, *m2; - m = do_vmark_first(dd->doc, ci->numeric); - m2 = do_vmark_last(dd->doc, ci->numeric); + if (ci->extra == 2) { + m = m2 = NULL; + } else { + m = do_vmark_first(dd->doc, ci->numeric); + m2 = do_vmark_last(dd->doc, ci->numeric); + } if (ci->extra == 1 && dd->point) m2 = do_vmark_at_point(dd->doc, dd->point, ci->numeric); @@ -858,6 +862,8 @@ DEF_CMD(docs_open) if (strcmp(ci->key, "Chr-h") == 0) renderer = "hex"; + if (strcmp(ci->key, "Chr-p") == 0) + renderer = "present"; if (strcmp(ci->key, "Chr-o") == 0) { struct pane *p2 = call_pane("OtherPane", ci->focus, 0, NULL, 0); @@ -896,6 +902,7 @@ void doc_make_docs(struct editor *ed) key_add(docs_map, "Chr-f", &docs_open); key_add(docs_map, "Chr-h", &docs_open); + key_add(docs_map, "Chr-p", &docs_open); key_add(docs_map, "Return", &docs_open); key_add(docs_map, "Chr-o", &docs_open); key_add(docs_map, "Chr-q", &docs_bury); diff --git a/core-mark.c b/core-mark.c index 5475fd8c..ec70f305 100644 --- a/core-mark.c +++ b/core-mark.c @@ -374,7 +374,8 @@ struct mark *doc_new_mark(struct doc *d, int view) { struct mark *ret; - if (view == MARK_POINT || view >= d->nviews || d->views[view].state != 1) + if (view == MARK_POINT || view >= d->nviews || + (view != MARK_UNGROUPED && d->views[view].state != 1)) return NULL; ret = calloc(sizeof(*ret), 1); ret->viewnum = view; @@ -981,8 +982,10 @@ struct mark *do_vmark_at_or_before(struct doc *d, struct mark *m, int view) /* Just use this, or nearby */ struct mark *vm2; while ((vm2 = vmark_next(vm)) != NULL && - mark_same(d, vm, vm2)) + mark_same(d, vm, m)) vm = vm2; + while (vm && vm->seq > m->seq && !mark_same(d, vm, m)) + vm = vmark_prev(vm); } return vm; } @@ -1046,7 +1049,7 @@ static void point_notify_change(struct doc *d, struct mark *p, struct mark *m) /* doc_notify_change is slower than point_notify_change, but only * requires a mark, not a point. - * A second mark should only be given in the first mark is a point + * A second mark should only be given if the first mark is a point */ void doc_notify_change(struct doc *d, struct mark *m, struct mark *m2) { diff --git a/core.h b/core.h index 21989138..02b8c4cf 100644 --- a/core.h +++ b/core.h @@ -548,6 +548,24 @@ static inline int call_xy(char *key, struct pane *focus, int numeric, return key_handle(&ci); } +static inline int call_xy7(char *key, struct pane *focus, int numeric, int extra, + char *str, char *str2, int x, int y, struct mark *m, struct mark *m2) +{ + struct cmd_info ci = {0}; + + ci.key = key; + ci.focus = focus; + ci.numeric = numeric; + ci.extra = extra; + ci.str = str; + ci.str2 = str2; + ci.x = x; + ci.y = y; + ci.mark = m; + ci.mark2 = m2; + return key_handle(&ci); +} + static inline int call7(char *key, struct pane *focus, int numeric, struct mark *m, char *str, int extra, char *str2, struct mark *m2) { diff --git a/doc-text.c b/doc-text.c index 2e35baa6..ae0acf15 100644 --- a/doc-text.c +++ b/doc-text.c @@ -1446,8 +1446,11 @@ static char *__text_get_attr(struct doc *d, struct mark *m, char *a = attr_get_str(d->attrs, attr, -1); if (a) return a; - if (strcmp(attr, "default-renderer") == 0) + if (strcmp(attr, "default-renderer") == 0) { +// if (t->fname && strcmp(t->fname + strlen(t->fname)-3, ".md") == 0) +// return "present"; return "lines"; + } if (strcmp(attr, "filename") == 0) return t->fname; return NULL; diff --git a/edlib.c b/edlib.c index a5b52835..f1d9664a 100644 --- a/edlib.c +++ b/edlib.c @@ -16,11 +16,15 @@ #include "core.h" -char WelcomeText[] = +static char WelcomeText[] = + ":H1:center,bg:green\n" + "\n" + "# heading\n" + "line\n" "\n" "Welcome to 'edlib' - the beginning of what one day might be an editor\n" "\n" - "Current functionality includes:\n" + "# Current functionality includes:\n" " splitting and closing windows (C-x 0,1,2,3)\n" " Resize current window (C-x },{,^)\n" " Move among windows (C-x o,O or mouse click)\n" @@ -95,6 +99,12 @@ int main(int argc, char *argv[]) editor_load_module(ed, "mode-emacs"); call5("global-set-keymap", global, 0, NULL, "mode-emacs", 0); + memset(&ci, 0, sizeof(ci)); + ci.home = ci.focus = global; + ci.key = "python-load"; + ci.str = "python/render-present.py"; + key_handle(&ci); + b = pane_attach(global, "tile", NULL, NULL); if (b) p = doc_from_text(b, "*Welcome*", WelcomeText); @@ -105,6 +115,23 @@ int main(int argc, char *argv[]) ci.str = "python/test.py"; key_handle(&ci); + /* New window.. */ + if (gtk) { + memset(&ci, 0, sizeof(ci)); + ci.home = ci.focus = vroot; + ci.key = "display-pygtk"; + cr.c = take_pane; + cr.p = NULL; + ci.comm2 = &cr.c; + key_handle(&ci); + root = pane_attach(cr.p, "input", NULL, NULL); + global = pane_attach(root, "messageline", NULL, NULL); + global = pane_attach(global, "global-keymap", NULL, NULL); + call5("global-set-keymap", global, 0, NULL, "mode-emacs", 0); + b = pane_attach(global, "tile", NULL, NULL); + p = doc_from_text(b, "*Welcome*", WelcomeText); + } + pane_refresh(&ed->root); while (call3("event:run", vroot, 0, NULL) == 1) ; diff --git a/lang-python.c b/lang-python.c index c309eb74..a7445c55 100644 --- a/lang-python.c +++ b/lang-python.c @@ -306,7 +306,7 @@ static PyObject *Pane_call(Pane *self, PyObject *args, PyObject *kwds) Py_INCREF(Py_None); return Py_None; } - if (rv < 0) { + if (rv == -1000) { PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed"); return NULL; } @@ -329,7 +329,7 @@ static PyObject *Pane_call_focus(Pane *self, PyObject *args, PyObject *kwds) Py_INCREF(Py_None); return Py_None; } - if (rv < 0) { + if (rv == -1000) { PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed"); return NULL; } @@ -357,7 +357,7 @@ static PyObject *Pane_call_xy(Pane *self, PyObject *args, PyObject *kwds) Py_INCREF(Py_None); return Py_None; } - if (rv < 0) { + if (rv == -1000) { PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed"); return NULL; } @@ -381,7 +381,7 @@ static PyObject *Pane_call_filter(Pane *self, PyObject *args, PyObject *kwds) Py_INCREF(Py_None); return Py_None; } - if (rv < 0) { + if (rv == -1000) { PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed"); return NULL; } @@ -421,6 +421,30 @@ static PyObject *Pane_rel(Pane *self, PyObject *args) return Py_BuildValue("ii", x, y); } +static PyObject *Pane_render_attach(Pane *self, PyObject *args) +{ + char *type = NULL; + struct pane *p; + int ret = PyArg_ParseTuple(args, "s", &type); + if (ret <= 0) + return NULL; + p = render_attach(type, self->pane); + if (!p) { + Py_INCREF(Py_None); + return Py_None; + } + return Pane_Frompane(p); +} + +static PyObject *Pane_damaged(Pane *self, PyObject *args) +{ + if (self->pane) { + pane_damaged(self->pane, DAMAGED_SIZE); + } + Py_INCREF(Py_None); + return Py_None; +} + static PyObject *Pane_close(Pane *self) { struct pane *p = self->pane; @@ -469,6 +493,10 @@ static PyMethodDef pane_methods[] = { "Convert absolute co-orders to pane-relative"}, {"add_notify", (PyCFunction)Pane_add_notify, METH_VARARGS, "Add notified for an event on some other pane"}, + {"render_attach", (PyCFunction)Pane_render_attach, METH_VARARGS, + "Attach a renderer to a pane"}, + {"damage", (PyCFunction)Pane_damaged, METH_VARARGS, + "Mark pane as damaged"}, {NULL} }; @@ -609,6 +637,52 @@ static PyGetSetDef pane_getseters[] = { {NULL} /* Sentinel */ }; +static PyObject *Pane_get_item(Pane *self, PyObject *key) +{ + char *k, *v; + if (!self->pane) { + PyErr_SetString(PyExc_TypeError, "Pane is NULL"); + return NULL; + } + if (!PyString_Check(key)) { + PyErr_SetString(PyExc_TypeError, "Key must be a string"); + return NULL; + } + k = PyString_AsString(key); + v = attr_get_str(self->pane->attrs, k, -1); + if (v) + return Py_BuildValue("s", v); + Py_INCREF(Py_None); + return Py_None; +} + +static int Pane_set_item(Pane *self, PyObject *key, PyObject *val) +{ + char *k, *v; + if (!self->pane) { + PyErr_SetString(PyExc_TypeError, "Pane is NULL"); + return -1; + } + if (!PyString_Check(key)) { + PyErr_SetString(PyExc_TypeError, "Key must be a string"); + return -1; + } + if (!PyString_Check(val)) { + PyErr_SetString(PyExc_TypeError, "value must be a string"); + return -1; + } + k = PyString_AsString(key); + v = PyString_AsString(val); + attr_set_str(&self->pane->attrs, k, v, -1); + return 0; +} + +static PyMappingMethods pane_mapping = { + .mp_length = NULL, + .mp_subscript = (binaryfunc)Pane_get_item, + .mp_ass_subscript = (objobjargproc)Pane_set_item, +}; + static PyTypeObject PaneType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ @@ -623,7 +697,7 @@ static PyTypeObject PaneType = { (reprfunc)pane_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ + &pane_mapping, /*tp_as_mapping*/ (hashfunc)pane_hash, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ @@ -690,6 +764,21 @@ static int mark_nosetview(Mark *m, PyObject *v, void *which) return -1; } +static PyObject *Mark_getseq(Mark *m, void *x) +{ + if (m->mark == NULL) { + PyErr_SetString(PyExc_TypeError, "Mark is NULL"); + return NULL; + } + return PyInt_FromLong(m->mark->seq); +} + +static int Mark_nosetseq(Mark *m, PyObject *v, void *which) +{ + PyErr_SetString(PyExc_TypeError, "Cannot set mark seq num"); + return -1; +} + static PyObject *mark_getdata(Mark *m, void *x) { PyObject *ret; @@ -731,15 +820,27 @@ static int mark_setdata(Mark *m, PyObject *v, void *x) PyObject *mark_compare(Mark *a, Mark *b, int op) { int ret = 0; - switch(op) { - case Py_LT: ret = mark_ordered(a->mark, b->mark); break; - case Py_LE: ret = mark_ordered(a->mark, b->mark); break; - case Py_GT: ret = mark_ordered(a->mark, b->mark); break; - case Py_GE: ret = mark_ordered(a->mark, b->mark); break; - case Py_EQ: ret = mark_ordered(a->mark, b->mark); break; - case Py_NE: ret = mark_ordered(a->mark, b->mark); break; - } - return ret ? Py_True : Py_False; + PyObject *rv; + if ((PyObject*)a == Py_None) + ret = (op == Py_LT || op == Py_LE || op == Py_NE); + else if ((PyObject*)b == Py_None) + ret = (op == Py_GT || op == Py_GE || op == Py_EQ); + else if (PyObject_TypeCheck(a, &MarkType) == 0 || + PyObject_TypeCheck(b, &MarkType) == 0) { + PyErr_SetString(PyExc_TypeError, "Mark compared with non-Mark"); + return NULL; + } else + switch(op) { + case Py_LT: ret = mark_ordered(a->mark, b->mark); break; + case Py_LE: ret = !mark_ordered(b->mark, a->mark); break; + case Py_GT: ret = mark_ordered(b->mark, a->mark); break; + case Py_GE: ret = !mark_ordered(a->mark, b->mark); break; + case Py_EQ: ret = !mark_ordered(a->mark, b->mark) && !mark_ordered(b->mark, a->mark); break; + case Py_NE: ret = mark_ordered(a->mark, b->mark) || mark_ordered(b->mark, a->mark); break; + } + rv = ret ? Py_True : Py_False; + Py_INCREF(rv); + return rv; } static PyGetSetDef mark_getseters[] = { @@ -749,6 +850,9 @@ static PyGetSetDef mark_getseters[] = { {"viewnum", (getter)mark_getview, (setter)mark_nosetview, "Index for view list", NULL}, + {"seq", + (getter)Mark_getseq, (setter)Mark_nosetseq, + "Seq number of mark", NULL}, {"data", (getter)mark_getdata, (setter)mark_setdata, "Application data", NULL}, @@ -831,20 +935,37 @@ static PyObject *Mark_next_any(Mark *self) return Py_None; } -static PyObject *Mark_dup(Mark *self) +static PyObject *Mark_dup(Mark *self, PyObject *args) { struct mark *new; + int ret; + int no_type = 0; if (!self->mark) { PyErr_SetString(PyExc_TypeError, "Mark is NULL"); return NULL; } - new = mark_dup(self->mark, 0); + ret = PyArg_ParseTuple(args, "|i", &no_type); + if (ret <= 0) + return NULL; + new = mark_dup(self->mark, no_type); if (new) return Mark_Frommark(new); Py_INCREF(Py_None); return Py_None; } +static PyObject *Mark_unlink(Mark *self) +{ + if (!self->mark) { + PyErr_SetString(PyExc_TypeError, "Mark is NULL"); + return NULL; + } + mark_free(self->mark); + self->mark = NULL; + Py_INCREF(Py_None); + return Py_None; +} + static PyMethodDef mark_methods[] = { {"to_mark", (PyCFunction)Mark_to_mark, METH_VARARGS, "Move one mark to another"}, @@ -854,8 +975,10 @@ static PyMethodDef mark_methods[] = { "previous vmark"}, {"next_any", (PyCFunction)Mark_next_any, METH_NOARGS, "next any_mark"}, - {"dup", (PyCFunction)Mark_dup, METH_NOARGS, - "duplicate a mark, preserving type"}, + {"dup", (PyCFunction)Mark_dup, METH_VARARGS, + "duplicate a mark, preserving type .. or not if '1' is given"}, + {"unlink", (PyCFunction)Mark_unlink, METH_NOARGS, + "unlink mark and discard it"}, {NULL} }; @@ -978,7 +1101,7 @@ static PyObject *Comm_call(Comm *c, PyObject *args, PyObject *kwds) Py_INCREF(Py_None); return Py_None; } - if (rv < 0) { + if (rv == -1000) { PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed"); return NULL; } @@ -1071,7 +1194,7 @@ static int get_cmd_info(struct cmd_info *ci, PyObject *args, PyObject *kwds) } for (i = 1; i < argc; i++) { a = PyTuple_GetItem(args, i); - if (Py_TYPE(a) == &PaneType) { + if (PyObject_TypeCheck(a, &PaneType)) { if (ci->focus == NULL) ci->focus = ((Pane*)a)->pane; else if (ci->home == NULL) diff --git a/lib-messageline.c b/lib-messageline.c index d9119439..7722aad5 100644 --- a/lib-messageline.c +++ b/lib-messageline.c @@ -18,7 +18,8 @@ struct mlinfo { char *message; struct pane *line; int height; /* height of line */ - int ascent; /* how for down to baseline */ + int ascent; /* how far down to baseline */ + int hidden; }; static void pane_str(struct pane *p, char *s, char *attr, int x, int y) @@ -39,6 +40,15 @@ DEF_CMD(messageline_handle) { struct mlinfo *mli = ci->home->data; + if (strcmp(ci->key, "Window:borderless") == 0) { + if (ci->numeric > 0) + mli->hidden = 1; + else + mli->hidden = 0; + pane_damaged(ci->home, DAMAGED_SIZE); + return 0; /* Allow other handlers */ + } + if (strcmp(ci->key, "Message") == 0) { if (ci->extra == 0 || mli->message == NULL) { free(mli->message); @@ -63,15 +73,19 @@ DEF_CMD(messageline_handle) mli->ascent = cr.i; } if (ci->home == mli->line) { - pane_resize(ci->home, 0, ci->home->parent->h - mli->height, - ci->home->parent->w, mli->height); + if (mli->hidden) + pane_resize(ci->home, 0, ci->home->parent->h, + ci->home->parent->w, mli->height); + else + pane_resize(ci->home, 0, ci->home->parent->h - mli->height, + ci->home->parent->w, mli->height); pane_clear(mli->line, "bg:cyan"); if (mli->message) pane_str(mli->line, mli->message, "bold,fg:red,bg:cyan", 0, 0 + mli->ascent); } else { pane_resize(ci->home, 0, 0, ci->home->parent->w, - ci->home->parent->h - mli->height); + ci->home->parent->h - (mli->hidden ? 0 : mli->height)); } return 1; } @@ -92,6 +106,7 @@ DEF_CMD(messageline_attach) mli->message = NULL; mli->height = 0; + mli->hidden = 0; ret = pane_register(p, 0, &messageline_handle, mli, NULL); mli->line = pane_register(p, 1, &messageline_handle, mli, NULL); pane_focus(ci->focus); diff --git a/lib-view.c b/lib-view.c index c86d4cde..08951afa 100644 --- a/lib-view.c +++ b/lib-view.c @@ -22,6 +22,7 @@ struct view_data { int border; + int old_border; int border_width, border_height; int line_height; int ascent; @@ -253,6 +254,7 @@ static struct pane *do_view_attach(struct pane *par, int border) vd = malloc(sizeof(*vd)); vd->border = border; + vd->old_border = border; vd->line_height = -1; vd->border_width = vd->border_height = -1; p = pane_register(par, 0, &view_handle, vd, NULL); @@ -317,12 +319,27 @@ DEF_CMD(view_click) return call3(key, p, num, NULL); } +DEF_CMD(view_borderless) +{ + struct pane *p = ci->home; + struct view_data *vd = p->data; + + if (ci->numeric > 0) { + vd->border = 0; + } else { + vd->border = vd->old_border; + } + pane_damaged(p, DAMAGED_SIZE); + return 0; /* Allow other handlers */ +} + void edlib_init(struct editor *ed) { view_map = key_alloc(); key_add(view_map, "Click-1", &view_click); key_add(view_map, "Press-1", &view_click); + key_add(view_map, "Window:borderless", &view_borderless); key_add(ed->commands, "attach-view", &view_attach); } diff --git a/python/display-pygtk.py b/python/display-pygtk.py index b86b6545..6ec87639 100644 --- a/python/display-pygtk.py +++ b/python/display-pygtk.py @@ -30,6 +30,12 @@ class EdDisplay(gtk.Window): def handle(self, key, **a): + if key == "Window:fullscreen": + if a['numeric'] > 0: + self.fullscreen() + else: + self.unfullscreen() + if key == "Close": self.pane.close() # FIXME close the window?? @@ -38,15 +44,16 @@ class EdDisplay(gtk.Window): if key == "pane-clear": f = a["focus"] if "str2" in a: + print "CLEAR", a['str2'] fg, bg = self.get_colours(a["str2"]) else: - fg, bg = self.get_colours("") + fg, bg = self.get_colours("bg:white") pm = self.get_pixmap(f) self.do_clear(pm, bg) return True if key == "text-size": - fd = self.extract_font(a["str2"]) + fd = self.extract_font(a["str2"], a['extra']) ctx = self.text.get_pango_context() metric = ctx.get_metrics(fd) self.text.modify_font(fd) @@ -73,6 +80,8 @@ class EdDisplay(gtk.Window): self.gc.set_foreground(cmap.alloc_color(gtk.gdk.color_parse("blue"))) if not self.bg: self.bg = t.window.new_gc() + cmap = t.get_colormap() + self.bg.set_foreground(cmap.alloc_color(gtk.gdk.color_parse("white"))) (x,y) = a["xy"] f = a["focus"] @@ -80,7 +89,7 @@ class EdDisplay(gtk.Window): attr = a['str2'] else: attr = '' - fd = self.extract_font(attr) + fd = self.extract_font(attr, a['extra']) ctx = self.text.get_pango_context() self.text.modify_font(fd) layout = self.text.create_pango_layout(a["str"]) @@ -89,9 +98,10 @@ class EdDisplay(gtk.Window): metric = ctx.get_metrics(fd) ascent = metric.get_ascent() / pango.SCALE ink,(lx,ly,width,height) = layout.get_pixel_extents() - self.bg.set_foreground(bg) - pm.draw_rectangle(self.bg, True, x+lx, y-ascent+ly, width, height) - pm.draw_layout(self.gc, x, y-ascent, layout, fg) + if bg: + self.bg.set_foreground(bg) + pm.draw_rectangle(self.bg, True, x+lx, y-ascent+ly, width, height) + pm.draw_layout(self.gc, x, y-ascent, layout, fg, bg) if a['numeric'] >= 0: cx,cy,cw,ch = layout.index_to_pos(a["numeric"]) if cw <= 0: @@ -107,6 +117,7 @@ class EdDisplay(gtk.Window): if f.parent.focus != f: extra = False f = f.parent + print x,cx,y,cy,cw,ch,extra,a['numeric'],len(a['str']) if extra: pm.draw_rectangle(self.gc, True, x+cx, y-ascent+cy, cw, ch); @@ -115,7 +126,42 @@ class EdDisplay(gtk.Window): s = unicode(a["str"][c:], "utf-8") l2 = pango.Layout(ctx) l2.set_text(s[0]) - pm.draw_layout(self.gc, x+cx, y-ascent+cy, l2, bg) + pm.draw_layout(self.gc, x+cx, y-ascent+cy, l2, bg, fg) + else: + pm.draw_rectangle(self.gc, False, x+cx, y-ascent+cy, + cw-1, ch-1); + return True + + if key == "image-display": + # 'str' is a file name of an image + # xy is location for top/left + # numeric/extra are max width/height + # I should use a pane-per-image but I'm in a rush + fl = a['str'] + f = a['focus'] + w = a['numeric'] + h = a['extra'] + if h == 0: + h = w + (x,y) = a['xy'] + try: + pb = gtk.gdk.pixbuf_new_from_file(fl) + except: + pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h) + pb.fill(0xff000000) + if pb.get_width() * h > pb.get_height() * w: + # image is wider than space, so reduce height + h2 = pb.get_height() * w / pb.get_width() + y += (h-h2)/2 + h = h2 + else: + # image is taller than space + w2 = pb.get_width() * h / pb.get_height() + x += (w-w2)/2 + w = w2 + scale = pb.scale_simple(w, h, gtk.gdk.INTERP_HYPER) + pm = self.get_pixmap(f) + pm.draw_pixbuf(self.gc, scale, 0, 0, x, y) return True if key == "Notify:Close": @@ -128,24 +174,33 @@ class EdDisplay(gtk.Window): styles=["oblique","italic","bold","small-caps"] - def extract_font(self, attrs): + def extract_font(self, attrs, scale): "Return a pango.FontDescription" family="mono" style="" size=10 + if scale <= 10: + scale = 1000 for word in attrs.split(','): if word in self.styles: style += " " + word + elif len(word) and word[0].isdigit(): + size = word elif word == "large": - size = 14 + size = 40 + elif word == "small": + size = 8.9 elif word[0:7] == "family:": family = word[7:] - return pango.FontDescription(family+' '+style+' '+str(size)) + fd = pango.FontDescription(family+' '+style+' '+str(size)) + if scale != 1000: + fd.set_size(fd.get_size() * scale / 1000) + return fd def get_colours(self, attrs): "Return a foreground and a background colour" - fg = "black" - bg = "white" + fg = None + bg = None inv = False for word in attrs.split(','): if word[0:3] == "fg:": @@ -155,12 +210,32 @@ class EdDisplay(gtk.Window): if word == "inverse": inv = True cmap = self.text.get_colormap() - fgc = cmap.alloc_color(gtk.gdk.color_parse(fg)) - bgc = cmap.alloc_color(gtk.gdk.color_parse(bg)) if inv: - return (bgc, fgc) + fg,bg = bg,fg + if fg is None: + fg = "white" + if bg is None: + bg = "black" + if fg is None: + fg = "black" + + if fg: + try: + c = gtk.gdk.color_parse(fg) + except: + c = gtk.gdk.color_parse("black") + fgc = cmap.alloc_color(c) + else: + fgc = None + if bg: + try: + c = gtk.gdk.color_parse(bg) + except: + c = gtk.gdk.color_parse("white") + bgc = cmap.alloc_color(c) else: - return (fgc, bgc) + bgc = None + return fgc, bgc def get_pixmap(self, p): if p in self.panes: diff --git a/python/render-present.py b/python/render-present.py new file mode 100644 index 00000000..a6e69d81 --- /dev/null +++ b/python/render-present.py @@ -0,0 +1,537 @@ +# -*- coding: utf-8 -*- +# Copyright Neil Brown ©2015 +# May be distributed under terms of GPLv2 - see file:COPYING + +# edlib rendering module for slide presentation. +# this reads lines from document, interprets as soemthing vaguely like +# markdown or restructuredtext, and displays a page at a time with formatting. +# +# Parsing is very line-oriented. +# - a line starting ":ptype:" defines attributes for given paragraph type +# - a line starting "#" is a heading, "##" is a level-2 heading (H1 H2 ...) +# - a line starting ">" is indented (I) +# - a line starting "-" is a list item. (L1) +# - a line starting " -" after a list item is a second-level list item (L2) +# - a line starting " " after a list item is more text in that list item +# - a line starting " " elsewhere is a code fragment (C) +# - a line starting "!" treats rest of line a name of file containing an image +# - other lines are text (P) +# +# A level-1 heading (H1) starts a new page. attribute lines immediately preceding +# it are part of the page and apply only to the page +# Attribute lines before the first page apply to whole document but can be over-ridden +# in a page. +# +# Within text *word* becomes bold (b), _word_ is italic (i) `word` is monospaced (m) +# +# attributes can be defined for paragraph types (H1,h2,I,L1,L2,C,P) and text types +# (b,i,m). Possible attributes for para or text are: +# normal oblique italic bold small-caps large NN family:fontfamily fg:colour bg:colour inverse +# Possible attributes for para only: +# left:size right:size centre space-after:size space-before:size +# +# All the above can also be given for "default" and apply before other defaults and +# global/local setting. "default" can also be given: +# xscale:size yscale:size +# +# All sizes are in "points". A "point" is either the height of the window divided by +# the yscale size, or the width divided by xscale size, whichever is smaller. +# +# global defaults are: + +default_attrs = "normal 10 family:sans fg:black bg:white left:5 space-after:1 space-before:1 xscale:10 yscale:40" + +import re + +def take(name, place, args, default=None): + if name in args: + place.append(args[name]) + else: + place.append(default) + return 1 + +class PresenterPane(edlib.Pane): + def __init__(self, focus): + edlib.Pane.__init__(self, focus, self.handle) + self.globals = {} + self.pageview = focus.call("doc:add-view") - 1 + self.attrview = focus.call("doc:add-view") - 1 + self.borderless = False + + def marks_same(self, m1, m2): + if isinstance(m1, edlib.Mark) and isinstance(m1, edlib.Mark): + return 1 == self.call("doc:mark-same", m1, m2); + return None + + def first_page(self): + m = [] + self.call("doc:vmark-get", self.pageview, lambda key, **a: take('mark', m, a)) + return m[0] + + def first_line(self): + m = [] + self.call("doc:vmark-get", self.attrview, lambda key, **a: take('mark', m, a)) + return m[0] + + def prev_page(self, m): + m2 = [] + self.call("doc:vmark-get", self.pageview, 3, m, lambda key, **a: take('mark2', m2, a)) + return m2[0] + + def prev_line(self, m): + m2 = [] + self.call("doc:vmark-get", self.attrview, 3, m, lambda key, **a: take('mark2', m2, a)) + return m2[0] + + def get_line_at(self, m): + # call render-line at m + line = [] + self.parent.call_filter("render-line", m, -1, lambda key, **a: take('str', line, a, '')) + return line[0] + + def get_line_before(self, m): + m2 = m.dup(1) + ret = self.parent.call_filter("render-line-prev", m2, 1) + if ret <= 0: + m2.unlink() + return None + l = self.get_line_at(m2) + m2.unlink() + return l + + def print_line_at(self, m): + x = m.dup(1) + l = self.get_line_at(x) + x.unlink() + return l[0:-1] + + def find_page(self, start, sof = False): + # Assumming 'm' is either start-of-file (if 'sof') or the start of + # a page, find the start of the next page. Return an untyped mark + # or None if there is no next page. + # A page starts with some ":attr:" lines and then a "# " line. + # If not 'sof' we first need to skip over the start of 'this' page. + skipping = not sof + m = start.dup(1) + maybe = None + globals = None + if sof: + globals = {} + extra = {} + while True: + if not maybe and not skipping: + maybe = m.dup() + l = self.get_line_at(m) + if not l: + # End of document + if maybe: + maybe.unlink() + m.unlink() + return None + if l[0] == ':': + # attribute - doesn't advance possible start + if globals is not None: + mt = re.match("^:([^: ]+):(.*)", l.strip('\n')) + if mt: + extra[mt.group(1)] = mt.group(2) + elif l[0:2] == "# ": + # level-1 heading. This is it if not skipping + if skipping: + skipping = False + else: + m.unlink() + if globals is not None: + self.globals = globals + if 'background' in globals: + self['background'] = globals['background'] + if 'scale' in globals: + self['scale'] = globals['scale'] + self.call("render-lines:redraw") + return maybe + else: + # not part of start-of-page + skipping = False + if maybe: + maybe.unlink() + maybe = None + if globals is not None: + globals.update(extra) + extra = {} + + def check_start(self, start): + # This was the start of a page, but might not be any more. Must check. + # Following lines must be ":attr:" or "# ". + # Preceeding line, if any must not be ":attr:" + m = start.dup(1) + l = self.get_line_at(m) + while l and l[0] == ':': + l = self.get_line_at(m) + m.unlink() + if not l or l[0:2] != '# ': + # No appropriate heading + return False + + m = start.dup(1) + l = self.get_line_before(m) + m.unlink() + if l and l[0] == ':': + return False + return True + + def find_pages(self, m): + # I need to find the start of the page that contains 'm' - though if m is before + # the first page, then I want the first page. + # I need to know that the 'next' page after than really is the start of the following + # page, or is None iff this is the last page. + # Each page mark has a 'valid' attribute which confirms it is at start of page, + # and a 'next-valid' attribute which confirms next page is valid. These are clear + # when nearby text changes. + # The PresenterPane has a 'first_valid' flag is the first page mark is valid. + # it is like 'next-valid'. + # If these check out, answer is easy. If they don't we need to revalidate anything + # that doesn't match and either set a flag or delete a mark or add a mark. Then + # try again. + pm = None + while not pm: + pm = self.prev_page(m) + if not pm: + # could be in pre-amble + pm = self.first_page() + if not pm: + # no marks at all. Create mark-at-start and find page + pma=[] + self.call("doc:vmark-get", self.pageview, 2, lambda key,**a: take('mark2', pma, a)) + pm = pma[0] + first = self.find_page(pm, True) + if first: + # Good, there is a page. + pm.to_mark(first) + self.first_valid = True + pm['valid'] = 'yes' + first.unlink() + pm = None + continue + # no pages at all + break + if not self.first_valid: + # We have a first page, but it might not be valid + pma=[] + self.call("doc:vmark-get", -2, 2, lambda key,**a: take('mark2', pma, a)) + t = pma[0] + first = self.find_page(t, True) + t.unlink() + if not first: + # no pages any more + pass + elif self.marks_same(pm, first): + # Oh good! + self.first_valid = True + pm['valid'] = 'yes' + first.unlink() + elif first < pm: + pm.to_mark(first) + self.first_valid = True + pm['valid'] = 'yes' + # but having moved,.. + pm['next-valid'] = 'no' + first.unlink() + else: + # .first_page() is before real first, just delete it + pm.unlink() + first.unlink() + pm = None + continue + # So this first page is valid, but is after the target. + # Fall through to checking next-page + elif pm['valid'] != 'yes': + # We have a previous page but need to validate it + if self.check_start(pm): + pm['valid'] = 'yes' + else: + if pm.prev(): + pm.prev()['next-valid'] = 'no' + else: + self.first_valid = False + pm.unlink() + pm = None + continue + + # pm is the start of this page, just need to check next + if pm['next-valid'] != 'yes': + next = self.find_page(pm) + if not next: + # this is last page + while pm.next(): + pm.next().unlink() + pm['next-valid'] = 'yes' + # all good now + elif pm.next() is None: + n = pm.dup() + n.to_mark(next) + next.unlink() + pm['next-valid'] = 'yes' + else: + if self.marks_same(next, pm.next()): + pm['next-valid'] = 'yes' + elif next <= pm.next(): + pm.next().to_mark(next) + else: + pm.next().unlink() + next.unlink() + pm = None + + if not self.first_valid: + # need to update globals + pma=[] + self.call("doc:vmark-get", -2, 2, lambda key,**a: take('mark2', pma, a)) + t = pma[0] + first = self.find_page(t, True) + if first: + self.first_valid = True + t.unlink() + + #x = self.first_page() + #while x: + # print "PAGE:", x.seq, self.print_line_at(x) + # x = x.next() + #print "RETURN", pm.seq + return pm + + # We maintain marks at the start of every line in the current page + # These have tags storing attributes extracted from the line. + # - If there are marks not in the current page, discard them. + # - If there are no marks, create them + # - get an attribute by searching back for marks of the right type + # If one has no type, validate it + # + def clean_lines(self, page): + next = page.next() + first = self.first_line() + while first and ((first < page and not self.marks_same(first,page)) or + (next and first > next and not self.marks_same(first, next))): + # first is outside this page + first.unlink() + first = self.first_line() + + def annotate(self, mark, line): + m = re.match("^:([^: ]+):(.*)", line.strip('\n')) + if m: + mark['type'] = 'attr:'+m.group(1) + mark['value'] = m.group(2) + else: + mark['type'] = 'text' + mark['value'] = '-None-' + + def mark_lines(self, page): + first = self.first_line() + if first: + return + # there are currently no lines + next = page.next() + la=[] + self.call("doc:vmark-get", self.attrview, 2, lambda key,**a: take('mark2', la, a)) + line = la[0] + + while not next or (line < next and not self.marks_same(line, next)): + # There is a line there that we care about - unless EOF + this = line.dup() + l = self.get_line_at(line) + if not l: + break + self.annotate(this, l) + line.unlink() + + def get_local_attr(self, m, attr): + t = 'attr:' + attr + l = self.prev_line(m) + while l: + if l['type'] == 'unknown': + # Ouch - need to re-evaluate. Just assume still at start-of-line + self.annotate(l, self.print_line_at(l)) + pass + if l['type'] == t: + return l['value'] + l = l.prev() + return None + + para_ptn = ['# ', '## ', '> ', '- ', ' ', '!', None, ''] + para_type = ['H1', 'H2', 'I', 'L1', 'C', 'IM', 'BL', 'P'] + defaults = { + 'H1': 'center,large,family:serif', + 'H2': 'right:20,fg:red,bold', + 'I': 'left:5,family:Mufferaw', + 'C': 'family:mono', + 'L1': 'fg:blue,left:40,family:sans', + 'P': 'left:30,family:sans,space-below:20', + 'BL': '3', + } + + def handle(self, key, **a): + if key == "render-line-prev": + # Go to start of page + here = a['mark'] + start = self.find_pages(here) + if not start: + return -2 + if start > here: + start = here + + if self.marks_same(start, here) and a['numeric'] == 1: + return -2 + here.to_mark(start) + return 1 + + if key == "render-line": + here = a['mark'] + cb = a['comm2'] + page = self.find_pages(here) + if not page: + # No pages at all + cb("callback", self) + return 1 + + if here < page: + here.to_mark(page) + + self.clean_lines(page) + self.mark_lines(page) + + end = page.next() + + line = None + while end is None or here < end: + lines = [] + self.parent.call_filter("render-line", here,a['numeric'], + lambda key2, **aa: take('str', lines, aa)) + if len(lines) == 0 or lines[0] is None: + line = None + break + line = lines[0] + if line[0] == ':': + #skip attributes + continue + line = line.strip("\n") + break + + if line is None: + cb("callback", self) + else: + mode = 'P' + for n in range(len(self.para_ptn)): + prefix = self.para_ptn[n] + if prefix == None and line == "": + mode = self.para_type[n] + break + if prefix is not None and prefix == line[0:len(prefix)]: + mode = self.para_type[n] + line = line[len(prefix):] + break + v = self.get_local_attr(here, mode) + if not v and mode in self.globals: + v = self.globals[mode] + if not v and mode in self.defaults: + v = self.defaults[mode] + if not v: + v = "" + + if mode == 'IM': + cb("callback", self, "") + return 1 + + line = re.sub("\*([A-Za-z0-9][^*<]*)\*", "\\1", line) + line = re.sub("`([A-Za-z0-9][^*<]*)`", "\\1", line) + line = "<"+v+">"+ line + "" + line += '\n' + if end and (here > end or self.marks_same(here,end)): + line += '\f' + cb("callback", self, line) + return 1 + + if key == "Notify:Replace": + m = a['mark'] + # A change has happened at 'm'. The following page might not + # be valid, and the previous may not be valid or have next-valid. + # If no previous, self.first_valid may not be. + page = self.prev_page(m) + if not page: + # m is before first page + page = self.first_page() + if page: + self.first_valid = False + page['valid'] = 'no' + # attributes probably changed so... + self.call("render-lines:redraw") + else: + page['valid'] = 'no' + page['next-valid'] = 'no' + page = page.next() + if page: + page['valid'] = 'no' + + l = self.prev_line(m) + if l: + if l['type'] and l['type'][0:5] == "attr:": + self.call("render-lines:redraw") + l['type'] = 'unknown' + l = l.next() + if l: + l['type'] = 'unknown' + return 1 + + if key == "Close": + # destroy all marks + self.release() + + if key == "Clone": + # Need to create a new PresenterPane I guess, then recurse on children + pass + + if key == "M-Chr-f": + if self.borderless: + self.call("Window:borderless", -1) + self.call("Window:fullscreen", -1) + self.borderless = False + else: + self.call("Window:borderless", 1) + self.call("Window:fullscreen", 1) + self.borderless = True + return 1 + + if key == "Next": + p = a['mark'] + page = self.find_pages(p) + if a['numeric'] < 0: + page = page.prev() + else: + page = page.next() + if page is not None: + p.to_mark(page) + self.call("Move-View-Pos", page) + a['focus'].damage(1) + return 1 + if key == "Prior": + p = a['mark'] + page = self.find_pages(p) + if a['numeric'] < 0: + page = page.next() + else: + page = page.prev() + if page is not None: + p.to_mark(page) + self.call("Move-View-Pos", page) + a['focus'].damage(1) + return 1 + + return None + +def present_attach(key, focus, comm2, **a): + p = PresenterPane(focus) + p['render-wrap'] = 'no' + p['background'] = 'color:yellow' + + p.call("Request:Notify:Replace") + p = p.render_attach("lines") + comm2('callback', p) + return 1 + +editor.call("global-set-command", pane, "render-present-attach", present_attach) diff --git a/render-lines.c b/render-lines.c index 72d0808d..918a19d6 100644 --- a/render-lines.c +++ b/render-lines.c @@ -25,7 +25,7 @@ * The returned line can contain attribute markings as . * is used to pop most recent attributes. << is used to include a literal '<'. * Lines generally contains UTF-8. Control character '\n' is end of line and - * '\t' tabs 1-8 spaces. Other control characters should be rendered as + * '\t' tabs 1-<8 spaces. Other control characters should be rendered as * e.g. ^X - in particular, nul must not appear in the line. * * We currently assume a constant-width font 1x1. @@ -73,11 +73,12 @@ * the slow way if the target point wasn't found. */ +#define _GNU_SOURCE for-asprintf #include #include #include #include - +#include #define MARK_DATA_PTR char #include "core.h" #include "misc.h" @@ -112,7 +113,7 @@ DEF_CMD(text_size_callback) #define CURS 2 static int draw_some(struct pane *p, int *x, int y, char *start, char **endp, - char *attr, int margin, int cursorpos, int cursx) + char *attr, int margin, int cursorpos, int cursx, int scale) { /* draw some text from 'start' for length 'len' into p[x,y]. * Update 'x' and 'startp' past what as drawn. @@ -124,7 +125,7 @@ static int draw_some(struct pane *p, int *x, int y, char *start, char **endp, */ int len = *endp - start; char *str = strndup(start, len); - struct call_return cr; + struct call_return cr = {0}; int max; int ret = WRAP; int rmargin = p->w - margin; @@ -135,24 +136,25 @@ static int draw_some(struct pane *p, int *x, int y, char *start, char **endp, } cr.c = text_size_callback; - call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c); + /* FIXME check error - python might crash */ + call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c); max = cr.i; if (max == 0 && ret == CURS) { /* must already have CURS position. */ ret = WRAP; rmargin = p->w - margin; - call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c); + call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c); max = cr.i; } if (max < len) { str[max] = 0; - call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c); + call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c); } if (y >= 0) { if (cursorpos >= 0 && cursorpos <= len) - call_xy("text-display", p, cursorpos, str, attr, *x, y); + call_xy7("text-display", p, cursorpos, scale, str, attr, *x, y, NULL, NULL); else - call_xy("text-display", p, -1, str, attr, *x, y); + call_xy7("text-display", p, -1, scale, str, attr, *x, y, NULL, NULL); } free(str); *x += cr.x; @@ -163,36 +165,65 @@ static int draw_some(struct pane *p, int *x, int y, char *start, char **endp, return ret; } -static void update_line_height_attr(struct pane *p, int *h, int *a, char *attr) +static void update_line_height_attr(struct pane *p, int *h, int *a, int *w, char *attr, char *str, int scale) { struct call_return cr; cr.c = text_size_callback; - call_comm7("text-size", p, -1, NULL, "M", 0, attr, &cr.c); + call_comm7("text-size", p, -1, NULL, str, scale, attr, &cr.c); if (cr.y > *h) *h = cr.y; if (cr.i2 > *a) *a = cr.i2; + if (w) + *w += cr.x; } -static void update_line_height(struct pane *p, int *h, int *a, char *line) +static void update_line_height(struct pane *p, int *h, int *a, int *w, int *center, char *line, + int scale) { struct buf attr; + char *tst = line; + char *l; + int above = 0, below = 0; + int attr_found = 0; buf_init(&attr); - update_line_height_attr(p, h, a, ""); + buf_append(&attr, ','); while (*line) { char c = *line++; char *st = line; if (c != '<' || *line == '<') continue; + if (line-1 > tst) { + l = strndup(tst, line-1-tst); + update_line_height_attr(p, h, a, w, buf_final(&attr), l, scale); + free(l); + } while (*line && line[-1] != '>') line += 1; + tst = line; if (st[0] != '/') { + char *c; buf_concat_len(&attr, st, line-st); attr.b[attr.len-1] = ','; buf_append(&attr, ','); - update_line_height_attr(p, h, a, buf_final(&attr)); + if (center && strstr(buf_final(&attr), ",center,")) + *center = 1; + if (center && (c=strstr(buf_final(&attr), ",left:")) != NULL) { + *center = atoi(c+6) * scale/1000; + } + if (center && (c=strstr(buf_final(&attr), ",right:")) != NULL) { + *center = - atoi(c+7) * scale/1000; + } + if ((c=strstr(buf_final(&attr), ",space-above:")) != NULL) { + above = atoi(c+13) * scale/1000; + } + if ((c=strstr(buf_final(&attr), ",space-below:")) != NULL) { + below = atoi(c+13) * scale/1000; + } + attr_found = 1; + update_line_height_attr(p, h, a, w, buf_final(&attr), "", scale); } else { /* strip back to ",," */ if (attr.len > 0) @@ -203,13 +234,64 @@ static void update_line_height(struct pane *p, int *h, int *a, char *line) attr.len -= 1; } } + if (line[-1] == '\n') + line -= 1; + if (line > tst || !attr_found) { + l = strndup(tst, line-tst); + update_line_height_attr(p, h, a, w, buf_final(&attr), l, scale); + free(l); + } + *h += above + below; + *a += above; free(buf_final(&attr)); } +static void render_image(struct pane *p, char *line, int *yp, int dodraw, int scale) +{ + char *cp, *e; + char *fname = NULL; + int width = p->w/2, height = p->h/2; + int y; + + while (*line == '<') + line ++; + while (line) { + e = strchr(line, ','); + if (!e) + e = strchr(line, '>'); + if (strncmp(line, "image:", 6) == 0) { + cp = line + 6; + if (e) + fname = strndup(cp, e-cp); + else + fname = strdup(cp); + } else if (strncmp(line, "width:", 6) == 0) { + width = atoi(line+6); + width = width * scale / 1000; + } else if (strncmp(line, "height:", 7) == 0) { + height = atoi(line+7); + height = height * scale / 1000; + } + if (e) + e++; + line = e; + } + y = *yp; + *yp = y + height; + if (!fname) + return; + if (!dodraw) { + free(fname); + return; + } + call_xy7("image-display", p, width, height, fname, NULL, (p->w - width)/2, y, + NULL, NULL); +} + /* render a line, with attributes and wrapping. Report line offset where * cursor point cx,cy is passed. -1 if never seen. */ -static void render_line(struct pane *p, char *line, int *yp, int dodraw, +static void render_line(struct pane *p, char *line, int *yp, int dodraw, int scale, int *cxp, int *cyp, int *offsetp) { int x = 0; @@ -226,17 +308,33 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw, int ascent = -1; int mwidth = -1; int ret = 0; + int twidth = 0; + int center = 0; + + /* Temporary hack (I hope) */ + if (strncmp(line, "prefix_len = x; + if (center == 1) + x += (p->w - x - twidth)/2; + if (center > 1) + x += center; + if (center < 0) + x = p->w - x - twidth + center; rl->line_height = line_height; - buf_init(&attr); buf_append(&attr, ' '); attr.len = 0; if (cxp && cyp && offsetp) { @@ -270,17 +368,22 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw, mwidth = cr.x; } - if (ret == WRAP) { + if (ret == WRAP && wrap) { char buf[2], *b; strcpy(buf, "\\"); b = buf+1; x = p->w - mwidth; draw_some(p, &x, dodraw?y+ascent:-1, buf, &b, "underline,fg:blue", - 0, -1, -1); + 0, -1, -1, scale); x = 0; y += line_height; } + if (ret == WRAP && !wrap) { + while (*line && *line != '\n' && *line != '<') + line += 1; + start = line; + } ch = *line; @@ -320,20 +423,22 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw, (line-start) * mwidth > p->w - x) { ret = draw_some(p, &x, dodraw?y+ascent:-1, start, &line, - buf_final(&attr), mwidth, CP, CX); + buf_final(&attr), wrap ? mwidth : 0, CP, CX, scale); start = line; } continue; } ret = draw_some(p, &x, dodraw?y+ascent:-1, start, &line, - buf_final(&attr), mwidth, CP, CX); + buf_final(&attr), wrap ? mwidth : 0, CP, CX, scale); + if (!wrap && ret == WRAP && line == start) + ret = 0; start = line; if (ret) continue; if (ch == '<') { line += 1; if (*line == '<') { - if (offset == start - line_start) + if (offset >= 0 && offset == start - line_start) offset += 1; start = line; line += 1; @@ -392,9 +497,10 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw, buf[2] = 0; b = buf+2; buf_concat(&attr, ",underline,fg:red"); + if (ch == '\f') buf[0] = 0; ret = draw_some(p, &x, dodraw?y+ascent:-1, buf, &b, buf_final(&attr), - mwidth*2, CP, CX); + wrap ? mwidth*2 : 0, CP, CX, scale); attr.len = l; start = line; } @@ -403,7 +509,7 @@ static void render_line(struct pane *p, char *line, int *yp, int dodraw, if (!*line && (line > start || offset == start - line_start)) { /* Some more to draw */ draw_some(p, &x, dodraw?y+ascent:-1, start, &line, - buf_final(&attr), mwidth, offset - (start - line_start), cx); + buf_final(&attr), wrap ? mwidth : 0, offset - (start - line_start), cx, scale); } if (y + line_height < cy || (y <= cy && x <= cx)) @@ -437,7 +543,7 @@ static struct mark *call_render_line_prev(struct pane *p, return NULL; } /* if n>0 we can fail because start-of-file was found before - * and newline. In that case ret == -2, and we return NULL. + * any newline. In that case ret == -2, and we return NULL. */ if (found) *found = (ret != -1); @@ -558,19 +664,44 @@ static int call_render_line_to_point(struct pane *p, struct mark *pm, return len; } +static int get_scale(struct pane *p) +{ + char *sc = pane_attr_get(p, "scale"); + int x, y; + int xscale, yscale, scale; + + if (!sc) + return 1000; + + if (sscanf(sc, "x:%d,y:%d", &x, &y) != 2) + return 1000; + + /* 'scale' is pixels per point times 1000. + * ":scale:x" is points across pane, so scale = p->w/x*1000 + */ + xscale = 1000 * p->w / x; + yscale = 1000 * p->h / y; + scale = (xscale < yscale) ? xscale: yscale; + if (scale < 10) + scale = 1000; + return scale; +} + static void find_lines(struct mark *pm, struct pane *p) { struct rl_data *rl = p->data; struct mark *top, *bot; struct mark *m; struct mark *start, *end; + int x; int y = 0; int offset; int found_start = 0, found_end = 0; int lines_above = 0, lines_below = 0; + int scale = get_scale(p); - if (pm->viewnum != MARK_POINT) - return; +// if (pm->viewnum != MARK_POINT) +// return; top = vmark_first(p, rl->typenum); bot = vmark_last(p, rl->typenum); @@ -586,14 +717,15 @@ static void find_lines(struct mark *pm, struct pane *p) m = vmark_next(start); end = m; - if (start->mdata) { - int x; - x = -1; lines_above = -1; y = 0; - render_line(p, start->mdata, &y, 0, &x, &lines_above, &offset); - lines_below = y - lines_above; - } - y = 1; - /* We have start/end of the focus line, and its height */ + x = -1; lines_above = -1; y = 0; + render_line(p, start->mdata ?: "", &y, 0, scale, &x, &lines_above, &offset); + //lines_below = y - lines_above; + lines_above = lines_below = 0; + + /* We have start/end of the focus line, and its height + * Rendering just that "line" uses a height of 'y', of which + * 'lines_above' is above the cursor, and 'lines_below' is below. + */ if (bot && !mark_ordered_or_same_pane(p, bot, start)) /* already before 'bot', so will never "cross over" bot, so * ignore 'bot' @@ -602,64 +734,85 @@ static void find_lines(struct mark *pm, struct pane *p) if (top && !mark_ordered_or_same_pane(p, end, top)) top = NULL; - rl->skip_lines = 0; - while (!((found_start && found_end) || y >= p->h - rl->header_lines)) { - if (!found_start) { + while ((!found_start || !found_end) && y < p->h - rl->header_lines) { + if (!found_start && lines_above == 0) { /* step backwards moving start */ - if (lines_above > 0) { - lines_above -= 1; - y += 1; + m = call_render_line_prev(p, mark_dup(start, 0), + 1, &rl->top_sol); + if (!m) { + /* no text before 'start' */ + found_start = 1; } else { - m = call_render_line_prev(p, mark_dup(start, 0), - 1, &rl->top_sol); - if (!m) { - /* no text before 'start' */ - found_start = 1; - } else { - int h = 0; - start = m; - if (!start->mdata) - call_render_line(p, start); - render_line(p, start->mdata, &h, 0, + int h = 0; + start = m; + if (!start->mdata) + call_render_line(p, start); + if (start->mdata) + render_line(p, start->mdata, &h, 0, scale, NULL, NULL, NULL); - if (h) { - lines_above = h - 1; - y += 1; - } else - found_start = 1; - } - if (bot && mark_ordered(start, bot)) - found_end = 1; + if (h) { + lines_above = h; + } else + found_start = 1; } + if (bot && mark_ordered(start, bot)) + found_end = 1; } - if (!found_end) { + if (!found_end && lines_below == 0) { /* step forwards */ - if (lines_below > 0) { - lines_below -= 1; - y += 1; - } else { - if (!end->mdata) - call_render_line(p, end); - if (!end->mdata) + if (!end->mdata) + call_render_line(p, end); + if (!end->mdata) + found_end = 1; + else { + int h = 0; + render_line(p, end->mdata, &h, 0, scale, + NULL, NULL, NULL); + if (end->mdata[strlen(end->mdata)-1] == '\f') + found_end = 1; + end = vmark_next(end); + ASSERT(end != NULL); + if (h) { + lines_below = h; + } else found_end = 1; - else { - int h = 0; - render_line(p, end->mdata, &h, 0, - NULL, NULL, NULL); - end = vmark_next(end); - ASSERT(end != NULL); - if (h) { - lines_below = h - 1; - y += 1; - } else - found_end = 1; - } - if (top && mark_ordered(top, end)) - found_start = 1; } + if (top && mark_ordered(top, end)) + found_start = 1; + } + if (lines_above > 0 && lines_below > 0) { + int consume = (lines_above > lines_below ? lines_below:lines_above) * 2; + if (consume > (p->h - rl->header_lines) - y) + consume = (p->h - rl->header_lines) - y; + if (lines_above > lines_below) { + lines_above -= consume - (consume/2); + lines_below -= consume/2; + } else { + lines_below -= consume - (consume/2); + lines_above -= consume/2; + } + y += consume; + /* We have just consumed all of one of lines_{above,below} + * so they are no longer both > 0 */ + } + if (found_end && lines_above) { + int consume = p->h - rl->header_lines - y; + if (consume > lines_above) + consume = lines_above; + lines_above -= consume; + y += consume; + } + if (found_start && lines_below) { + int consume = p->h - rl->header_lines - y; + if (consume > lines_below) + consume = lines_below; + lines_below -= consume; + y += consume; } } rl->skip_lines = lines_above; + if (start->seq > end->seq) + end = start; /* Now discard any marks outside start-end */ while ((m = vmark_prev(start)) != NULL) { free(m->mdata); @@ -682,17 +835,25 @@ static void render(struct mark *pm, struct pane *p) struct mark *m, *m2; int restarted = 0; char *hdr; + char *bg; + int scale = get_scale(p); hdr = pane_attr_get(p, "heading"); if (hdr && !*hdr) hdr = NULL; restart: - pane_clear(p, NULL); - y = 0; + bg = pane_attr_get(p, "background"); + if (bg && strncmp(bg, "color:", 6) == 0) { + char *a; + asprintf(&a, "bg:%s", bg+6); + pane_clear(p, a); + free(a); + } else + pane_clear(p, NULL); if (hdr) { rl->header_lines = 0; - render_line(p, hdr, &y, 1, NULL, NULL, NULL); + render_line(p, hdr, &y, 1, scale, NULL, NULL, NULL); rl->header_lines = y; } y -= rl->skip_lines; @@ -713,7 +874,7 @@ restart: int len = call_render_line_to_point(p, pm, m); rl->cursor_line = y; - render_line(p, m->mdata ?: "", &y, 1, &p->cx, &p->cy, &len); + render_line(p, m->mdata ?: "", &y, 1, scale, &p->cx, &p->cy, &len); if (p->cy < 0) p->cx = -1; if (!rl->do_wrap && p->cy >= 0 && p->cx < rl->prefix_len) { @@ -744,7 +905,9 @@ restart: } } } else - render_line(p, m->mdata?:"", &y, 1, NULL, NULL, NULL); + render_line(p, m->mdata?:"", &y, 1, scale, NULL, NULL, NULL); + if (m->mdata && m->mdata[strlen(m->mdata)-1]=='\f') + break; if (!m2) break; m = m2; @@ -842,6 +1005,7 @@ DEF_CMD(render_lines_move) struct rl_data *rl = p->data; struct mark *top; int pagesize = rl->line_height; + int scale = get_scale(p); top = vmark_first(p, rl->typenum); if (!top) @@ -870,7 +1034,7 @@ DEF_CMD(render_lines_move) call_render_line(p, top); if (top->mdata == NULL) break; - render_line(p, top->mdata, &y, 0, NULL, NULL, NULL); + render_line(p, top->mdata, &y, 0, scale, NULL, NULL, NULL); rl->skip_lines = y; } } else { @@ -882,7 +1046,7 @@ DEF_CMD(render_lines_move) call_render_line(p, top); if (top->mdata == NULL) break; - render_line(p, top->mdata, &y, 0, NULL, NULL, NULL); + render_line(p, top->mdata, &y, 0, scale, NULL, NULL, NULL); if (rl->skip_lines + rpt < y) { rl->skip_lines += rpt; break; @@ -913,6 +1077,7 @@ DEF_CMD(render_lines_set_cursor) int y = rl->header_lines - rl->skip_lines; int found = 0; int cihx, cihy; + int scale = get_scale(p); render_lines_other_move_func(ci); @@ -926,7 +1091,7 @@ DEF_CMD(render_lines_set_cursor) cihy = y; while (y <= cihy && m && m->mdata) { int cx = cihx, cy = cihy, o = -1; - render_line(p, m->mdata, &y, 0, &cx, &cy, &o); + render_line(p, m->mdata, &y, 0, scale, &cx, &cy, &o); if (o >= 0) { struct mark *m2 = call_render_line_offset(p, m, o); if (m2) { @@ -955,7 +1120,7 @@ DEF_CMD(render_lines_move_pos) bot = vmark_last(p, rl->typenum); if (top && bot && mark_ordered(top, pm) && - mark_ordered(pm, bot)) + mark_ordered(pm, bot) && !mark_same_pane(p, pm, bot, NULL)) /* pos already displayed */ return 1; find_lines(pm, ci->home); @@ -978,6 +1143,7 @@ DEF_CMD(render_lines_move_line) struct cmd_info ci2 = {0}; int target_x, target_y; int o = -1; + int scale = get_scale(p); rl->ignore_point = 0; @@ -1017,7 +1183,7 @@ DEF_CMD(render_lines_move_line) pane_damaged(p, DAMAGED_CONTENT); return 1; } - render_line(p, start->mdata, &y, 0, &target_x, &target_y, &o); + render_line(p, start->mdata, &y, 0, scale, &target_x, &target_y, &o); /* 'o' is the distance from start-of-line of the target */ if (o >= 0) { struct mark *m2 = call_render_line_offset( @@ -1096,6 +1262,7 @@ DEF_CMD(render_lines_redraw) free(m->mdata); m->mdata = NULL; } + pane_damaged(p, DAMAGED_SIZE); return 1; }