Implement control code for deleting images

This commit is contained in:
Kovid Goyal 2017-10-08 22:51:00 +05:30
parent 3f9f5ae076
commit 747f7b076d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 156 additions and 14 deletions

View File

@ -256,6 +256,40 @@ they are semi-transparent. You can specify z-index values using the `z` key.
Negative z-index values mean that the images will be drawn under the text. This
allows rendering of text on top of images.
== Deleting images
Images can be deleted by using the delete action `a=d`. If specified without any
other keys, it will delete all images visible on screen. To delete specific images,
use the `d` key as described in the table below. Note that each value of d has
both a lowercase and an uppercase variant. The lowercase variant only deletes the
images without necessarily freeing up the stored image data, so that the images can be
re-displayed without needing to resend the data. The uppercase variants will delete
the image data as well, provided that the image is not referenced elsewhere, such as in the
scrollback buffer. The values of the `x` and `y` keys are the same as cursor positions (i.e.
x=1, y=1 is the top left cell).
|===
| Value of `d` | Meaning
| `a` or `A` | Delete all images visible on screen
| `i` or `I` | Delete all images with the specified id, specified using the `i` key.
| `c` or `C` | Delete all images that intersect with the current cursor position.
| `p` or `P` | Delete all images that intersect a specific cell, the cell is specified using the `x` and `y` keys
| `q` or `Q` | Delete all images that intersect a specific cell having a specific z-index. The cell and z-index is specified using the `x`, `y` and `z` keys.
| `x` or `X` | Delete all images that intersect the specified column, specified using the `x` key.
| `y` or `Y` | Delete all images that intersect the specified row, specified using the `y` key.
| `z` or `Z` | Delete all images that have the specified z-index, specified using the `z` key.
Some examples:
```
<ESC>_Ga=d<ESC>\ # delete all visible images
<ESC>_Ga=d,i=10<ESC>\ # delete the image with id=10
<ESC>_Ga=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
<ESC>_Ga=P,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4)
```
|===
== Control data reference
The table below shows all the control data keys as well as what values they can
@ -265,7 +299,7 @@ take, and the default value they take when missing. All integers are 32-bit.
|===
|Key | Value | Default | Description
| `a` | Single character. `(t, T, q, p)` | `t` | The overall action this graphics command is performing.
| `a` | Single character. `(t, T, q, p, d)` | `t` | The overall action this graphics command is performing.
4+^.^h| Keys for image transmission
@ -290,6 +324,11 @@ take, and the default value they take when missing. All integers are 32-bit.
| `c` | Positive integer | `0` | The number of columns to display the image over
| `r` | Positive integer | `0` | The number of rows to display the image over
| `z` | Integer | `0` | The *z-index* vertical stacking order of the image
4+^.^h| Keys for deleting images
| `d` | Single character. `(a, A, c, C, p, P, q, Q, x, X, y, Y, z, Z)`. | `a` | What to delete.
|===
@ -298,7 +337,7 @@ take, and the default value they take when missing. All integers are 32-bit.
When resetting the terminal, all images that are visible on the screen must be
cleared. When switching from the main screen to the alternate screen buffer
(1049 private mode) all images in the alternate screen must be cleared, just as
all text is cleared. The clear command (usually `<ESC>[H<ESC>[2J`) should also
all text is cleared. The clear screen escape code (usually `<ESC>[2J`) should also
clear all images. This is so that the clear command works.
The other commands to erase text must have no effect on graphics.

View File

@ -561,6 +561,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
}
c->x += num_cols; c->y += num_rows - 1;
ref->effective_num_rows = num_rows;
ref->effective_num_cols = num_cols;
}
static int
@ -630,7 +631,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
// Image lifetime/scrolling {{{
static inline void
filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRef*, Image*, const void*)) {
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*)) {
Image *img; ImageRef *ref;
size_t i, j;
@ -643,7 +644,7 @@ filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe
remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--);
}
}
if (img->refcnt == 0) {
if (img->refcnt == 0 && (free_images || img->client_id == 0)) {
free_image(img);
remove_from_array(self->images, sizeof(Image), i, self->image_count--);
}
@ -702,7 +703,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) {
void
grman_scroll_images(GraphicsManager *self, const ScrollData *data) {
filter_refs(self, data, data->has_margins ? scroll_filter_margins_func : scroll_filter_func);
filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func);
}
static inline bool
@ -712,7 +713,71 @@ clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) {
void
grman_clear(GraphicsManager *self) {
filter_refs(self, NULL, clear_filter_func);
filter_refs(self, NULL, true, clear_filter_func);
}
static inline bool
id_filter_func(ImageRef UNUSED *ref, Image *img, const void *data) {
uint32_t iid = *(uint32_t*)data;
return img->client_id == iid;
}
static inline bool
x_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) {
const GraphicsCommand *g = data;
return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
}
static inline bool
y_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) {
const GraphicsCommand *g = data;
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows));
}
static inline bool
z_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) {
const GraphicsCommand *g = data;
return ref->z_index == g->z_index;
}
static inline bool
point_filter_func(ImageRef *ref, Image *img, const void *data) {
return x_filter_func(ref, img, data) && y_filter_func(ref, img, data);
}
static inline bool
point3d_filter_func(ImageRef *ref, Image *img, const void *data) {
return z_filter_func(ref, img, data) && point_filter_func(ref, img, data);
}
static void
handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty) {
static GraphicsCommand d;
switch (g->delete_action) {
#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func); *is_dirty = true; break
#define D(l, u, data, func) case l: case u: I(u, data, func)
#define G(l, u, func) D(l, u, g, func)
case 0:
D('a', 'A', NULL, clear_filter_func);
D('i', 'I', &g->id, id_filter_func);
G('p', 'P', point_filter_func);
G('q', 'Q', point3d_filter_func);
G('x', 'X', x_filter_func);
G('y', 'Y', y_filter_func);
G('z', 'Z', z_filter_func);
case 'c':
case 'C':
d.x_offset = c->x + 1; d.y_offset = c->y + 1;
I('C', &d, point_filter_func);
default:
REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action);
break;
#undef G
#undef D
#undef I
}
}
// }}}
@ -737,12 +802,15 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
break;
case 'p':
if (!g->id) {
REPORT_ERROR("%s", "Put graphics command without image id");
REPORT_ERROR("Put graphics command without image id");
break;
}
handle_put_command(self, g, c, is_dirty, NULL);
ret = create_add_response(self, true, g->id);
break;
case 'd':
handle_delete_command(self, g, c, is_dirty);
break;
default:
REPORT_ERROR("Unknown graphics command action: %c", g->action);
break;

View File

@ -8,7 +8,7 @@
#include "data-types.h"
typedef struct {
unsigned char action, transmission_type, compressed;
unsigned char action, transmission_type, compressed, delete_action;
uint32_t format, more, id, data_sz, data_offset;
uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines, cell_x_offset, cell_y_offset;
int32_t z_index;
@ -34,7 +34,7 @@ typedef struct {
typedef struct {
uint32_t src_width, src_height, src_x, src_y;
uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows;
uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows, effective_num_cols;
int32_t z_index;
int32_t start_row, start_column;
ImageRect src_rect;

View File

@ -540,6 +540,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
enum GR_STATES state = KEY, value_state = FLAG;
enum KEYS {
action='a',
delete_action='d',
transmission_type='t',
compressed='o',
format = 'f',
@ -577,7 +578,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
switch(key) {
#define KS(n, vs) case n: state = EQUAL; value_state = vs; break
#define U(x) KS(x, UINT)
KS(action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT);
KS(action, FLAG); KS(delete_action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT);
U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset);
#undef U
#undef KS
@ -598,7 +599,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
case FLAG:
switch(key) {
#define F(a) case a: g.a = screen->parser_buf[pos++] & 0xff; break
F(action); F(transmission_type); F(compressed);
F(action); F(delete_action); F(transmission_type); F(compressed);
default:
break;
}
@ -675,8 +676,8 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
#define A(x) #x, g.x
#define U(x) #x, (unsigned int)(g.x)
#define I(x) #x, (int)(g.x)
REPORT_VA_COMMAND("s {sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command",
A(action), A(transmission_type), A(compressed),
REPORT_VA_COMMAND("s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command",
A(action), A(delete_action), A(transmission_type), A(compressed),
U(format), U(more), U(id), U(data_sz), U(data_offset),
U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), U(cell_x_offset), U(cell_y_offset),
U(payload_sz), I(z_index),

View File

@ -292,3 +292,37 @@ class TestGraphics(BaseTest):
s.index()
s.reset()
self.ae(s.grman.image_count, 1)
def test_gr_delete(self):
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
def delete(ac=None, **kw):
cmd = 'a=d'
if ac:
cmd += ',d={}'.format(ac)
if kw:
cmd += ',' + ','.join('{}={}'.format(k, v) for k, v in kw.items())
send_command(s, cmd)
put_image(s, cw, ch)
delete()
self.ae(len(layers(s)), 0), self.ae(s.grman.image_count, 1)
delete('A')
self.ae(s.grman.image_count, 0)
iid = put_image(s, cw, ch)[0]
delete('I', i=iid)
self.ae(s.grman.image_count, 0)
s.reset()
put_image(s, cw, ch)
put_image(s, cw, ch)
delete('C')
self.ae(s.grman.image_count, 2)
s.cursor_position(1, 1)
delete('C')
self.ae(s.grman.image_count, 1)
delete('P', x=2, y=1)
self.ae(s.grman.image_count, 0)
put_image(s, cw, ch, z=9)
delete('Z', z=9)
self.ae(s.grman.image_count, 0)

View File

@ -202,7 +202,7 @@ class TestParser(BaseTest):
for p, v in tuple(k.items()):
if isinstance(v, str) and p != 'payload':
k[p] = v.encode('ascii')
for f in 'action transmission_type compressed'.split():
for f in 'action delete_action transmission_type compressed'.split():
k.setdefault(f, b'\0')
for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width'
' num_cells num_lines cell_x_offset cell_y_offset z_index').split():