Added thumbnail zooming...
- Key mappings +/- are now general commands - Use JPG as thumbnail cache file format instead of PNG - Fixes issue #161
This commit is contained in:
parent
b2dbd2fed3
commit
7b91e10f22
@ -101,6 +101,8 @@ of small previews is displayed, making it easy to choose an image to open.
|
|||||||
r Reload image
|
r Reload image
|
||||||
D Remove image from file list and go to next image
|
D Remove image from file list and go to next image
|
||||||
Ctrl-h,j,k,l Scroll one window width/height left/down/up/right
|
Ctrl-h,j,k,l Scroll one window width/height left/down/up/right
|
||||||
|
+ Zoom in
|
||||||
|
- Zoom out
|
||||||
m Mark/unmark current image
|
m Mark/unmark current image
|
||||||
M Reverse all image marks
|
M Reverse all image marks
|
||||||
Ctrl-m Remove all image marks
|
Ctrl-m Remove all image marks
|
||||||
@ -123,8 +125,6 @@ of small previews is displayed, making it easy to choose an image to open.
|
|||||||
h,j,k,l Scroll image 1/5 of window width/height or [count] pixels
|
h,j,k,l Scroll image 1/5 of window width/height or [count] pixels
|
||||||
left/down/up/right (also with arrow keys)
|
left/down/up/right (also with arrow keys)
|
||||||
H,J,K,L Scroll to left/bottom/top/right image edge
|
H,J,K,L Scroll to left/bottom/top/right image edge
|
||||||
+ Zoom in
|
|
||||||
- Zoom out
|
|
||||||
= Set zoom level to 100%, or [count]%
|
= Set zoom level to 100%, or [count]%
|
||||||
w Set zoom level to 100%, but fit large images into window
|
w Set zoom level to 100%, but fit large images into window
|
||||||
W Fit image to window
|
W Fit image to window
|
||||||
|
26
commands.c
26
commands.c
@ -193,6 +193,20 @@ bool cg_scroll_screen(arg_t a)
|
|||||||
return tns_scroll(&tns, dir, true);
|
return tns_scroll(&tns, dir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cg_zoom(arg_t a)
|
||||||
|
{
|
||||||
|
long d = (long) a;
|
||||||
|
|
||||||
|
if (mode == MODE_THUMB)
|
||||||
|
return tns_zoom(&tns, d);
|
||||||
|
else if (d > 0)
|
||||||
|
return img_zoom_in(&img);
|
||||||
|
else if (d < 0)
|
||||||
|
return img_zoom_out(&img);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool cg_toggle_image_mark(arg_t a)
|
bool cg_toggle_image_mark(arg_t a)
|
||||||
{
|
{
|
||||||
files[fileidx].marked = !files[fileidx].marked;
|
files[fileidx].marked = !files[fileidx].marked;
|
||||||
@ -386,18 +400,6 @@ bool ci_drag(arg_t a)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ci_zoom(arg_t a)
|
|
||||||
{
|
|
||||||
long scale = (long) a;
|
|
||||||
|
|
||||||
if (scale > 0)
|
|
||||||
return img_zoom_in(&img);
|
|
||||||
else if (scale < 0)
|
|
||||||
return img_zoom_out(&img);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ci_set_zoom(arg_t a)
|
bool ci_set_zoom(arg_t a)
|
||||||
{
|
{
|
||||||
return img_zoom(&img, (prefix ? prefix : (long) a) / 100.0);
|
return img_zoom(&img, (prefix ? prefix : (long) a) / 100.0);
|
||||||
|
@ -8,6 +8,7 @@ G_CMD(remove_image)
|
|||||||
G_CMD(first)
|
G_CMD(first)
|
||||||
G_CMD(n_or_last)
|
G_CMD(n_or_last)
|
||||||
G_CMD(scroll_screen)
|
G_CMD(scroll_screen)
|
||||||
|
G_CMD(zoom)
|
||||||
G_CMD(toggle_image_mark)
|
G_CMD(toggle_image_mark)
|
||||||
G_CMD(reverse_marks)
|
G_CMD(reverse_marks)
|
||||||
G_CMD(unmark_all)
|
G_CMD(unmark_all)
|
||||||
@ -20,7 +21,6 @@ I_CMD(toggle_animation)
|
|||||||
I_CMD(scroll)
|
I_CMD(scroll)
|
||||||
I_CMD(scroll_to_edge)
|
I_CMD(scroll_to_edge)
|
||||||
I_CMD(drag)
|
I_CMD(drag)
|
||||||
I_CMD(zoom)
|
|
||||||
I_CMD(set_zoom)
|
I_CMD(set_zoom)
|
||||||
I_CMD(fit_to_win)
|
I_CMD(fit_to_win)
|
||||||
I_CMD(rotate)
|
I_CMD(rotate)
|
||||||
|
12
config.def.h
12
config.def.h
@ -79,6 +79,10 @@ static const keymap_t keys[] = {
|
|||||||
{ ControlMask, XK_Up, g_scroll_screen, (arg_t) DIR_UP },
|
{ ControlMask, XK_Up, g_scroll_screen, (arg_t) DIR_UP },
|
||||||
{ ControlMask, XK_l, g_scroll_screen, (arg_t) DIR_RIGHT },
|
{ ControlMask, XK_l, g_scroll_screen, (arg_t) DIR_RIGHT },
|
||||||
{ ControlMask, XK_Right, g_scroll_screen, (arg_t) DIR_RIGHT },
|
{ ControlMask, XK_Right, g_scroll_screen, (arg_t) DIR_RIGHT },
|
||||||
|
{ 0, XK_plus, g_zoom, (arg_t) +1 },
|
||||||
|
{ 0, XK_KP_Add, g_zoom, (arg_t) +1 },
|
||||||
|
{ 0, XK_minus, g_zoom, (arg_t) -1 },
|
||||||
|
{ 0, XK_KP_Subtract, g_zoom, (arg_t) -1 },
|
||||||
{ 0, XK_m, g_toggle_image_mark, (arg_t) None },
|
{ 0, XK_m, g_toggle_image_mark, (arg_t) None },
|
||||||
{ 0, XK_M, g_reverse_marks, (arg_t) None },
|
{ 0, XK_M, g_reverse_marks, (arg_t) None },
|
||||||
{ ControlMask, XK_m, g_unmark_all, (arg_t) None },
|
{ ControlMask, XK_m, g_unmark_all, (arg_t) None },
|
||||||
@ -119,10 +123,6 @@ static const keymap_t keys[] = {
|
|||||||
{ 0, XK_J, i_scroll_to_edge, (arg_t) DIR_DOWN },
|
{ 0, XK_J, i_scroll_to_edge, (arg_t) DIR_DOWN },
|
||||||
{ 0, XK_K, i_scroll_to_edge, (arg_t) DIR_UP },
|
{ 0, XK_K, i_scroll_to_edge, (arg_t) DIR_UP },
|
||||||
{ 0, XK_L, i_scroll_to_edge, (arg_t) DIR_RIGHT },
|
{ 0, XK_L, i_scroll_to_edge, (arg_t) DIR_RIGHT },
|
||||||
{ 0, XK_plus, i_zoom, (arg_t) +1 },
|
|
||||||
{ 0, XK_KP_Add, i_zoom, (arg_t) +1 },
|
|
||||||
{ 0, XK_minus, i_zoom, (arg_t) -1 },
|
|
||||||
{ 0, XK_KP_Subtract, i_zoom, (arg_t) -1 },
|
|
||||||
{ 0, XK_equal, i_set_zoom, (arg_t) 100 },
|
{ 0, XK_equal, i_set_zoom, (arg_t) 100 },
|
||||||
{ 0, XK_w, i_fit_to_win, (arg_t) SCALE_DOWN },
|
{ 0, XK_w, i_fit_to_win, (arg_t) SCALE_DOWN },
|
||||||
{ 0, XK_W, i_fit_to_win, (arg_t) SCALE_FIT },
|
{ 0, XK_W, i_fit_to_win, (arg_t) SCALE_FIT },
|
||||||
@ -153,8 +153,8 @@ static const button_t buttons[] = {
|
|||||||
{ ShiftMask, 5, i_scroll, (arg_t) DIR_RIGHT },
|
{ ShiftMask, 5, i_scroll, (arg_t) DIR_RIGHT },
|
||||||
{ 0, 6, i_scroll, (arg_t) DIR_LEFT },
|
{ 0, 6, i_scroll, (arg_t) DIR_LEFT },
|
||||||
{ 0, 7, i_scroll, (arg_t) DIR_RIGHT },
|
{ 0, 7, i_scroll, (arg_t) DIR_RIGHT },
|
||||||
{ ControlMask, 4, i_zoom, (arg_t) +1 },
|
{ ControlMask, 4, g_zoom, (arg_t) +1 },
|
||||||
{ ControlMask, 5, i_zoom, (arg_t) -1 },
|
{ ControlMask, 5, g_zoom, (arg_t) -1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
12
sxiv.1
12
sxiv.1
@ -140,6 +140,12 @@ Scroll up one screen height.
|
|||||||
.BR Ctrl-l ", " Ctrl-Right
|
.BR Ctrl-l ", " Ctrl-Right
|
||||||
Scroll right one screen width.
|
Scroll right one screen width.
|
||||||
.TP
|
.TP
|
||||||
|
.BR +
|
||||||
|
Zoom in.
|
||||||
|
.TP
|
||||||
|
.B \-
|
||||||
|
Zoom out.
|
||||||
|
.TP
|
||||||
.B m
|
.B m
|
||||||
Mark/unmark the current image.
|
Mark/unmark the current image.
|
||||||
.TP
|
.TP
|
||||||
@ -251,12 +257,6 @@ Scroll to top image edge.
|
|||||||
Scroll to right image edge.
|
Scroll to right image edge.
|
||||||
.SS Zooming
|
.SS Zooming
|
||||||
.TP
|
.TP
|
||||||
.BR +
|
|
||||||
Zoom in.
|
|
||||||
.TP
|
|
||||||
.B \-
|
|
||||||
Zoom out.
|
|
||||||
.TP
|
|
||||||
.B =
|
.B =
|
||||||
Set zoom level to 100%, or
|
Set zoom level to 100%, or
|
||||||
.IR count %.
|
.IR count %.
|
||||||
|
104
thumbs.c
104
thumbs.c
@ -17,7 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#define _POSIX_C_SOURCE 200112L
|
#define _POSIX_C_SOURCE 200112L
|
||||||
#define _THUMBS_CONFIG
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -37,7 +36,7 @@ void exif_auto_orientate(const fileinfo_t*);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static char *cache_dir;
|
static char *cache_dir;
|
||||||
static const int thumb_dim = THUMB_SIZE + 10;
|
static const int thumb_size[] = { 32, 64, 96, 128, 160 };
|
||||||
|
|
||||||
char* tns_cache_filepath(const char *filepath)
|
char* tns_cache_filepath(const char *filepath)
|
||||||
{
|
{
|
||||||
@ -51,7 +50,7 @@ char* tns_cache_filepath(const char *filepath)
|
|||||||
/* don't cache images inside the cache directory! */
|
/* don't cache images inside the cache directory! */
|
||||||
len = strlen(cache_dir) + strlen(filepath) + 6;
|
len = strlen(cache_dir) + strlen(filepath) + 6;
|
||||||
cfile = (char*) s_malloc(len);
|
cfile = (char*) s_malloc(len);
|
||||||
snprintf(cfile, len, "%s/%s.png", cache_dir, filepath + 1);
|
snprintf(cfile, len, "%s/%s.jpg", cache_dir, filepath + 1);
|
||||||
}
|
}
|
||||||
return cfile;
|
return cfile;
|
||||||
}
|
}
|
||||||
@ -79,21 +78,19 @@ Imlib_Image tns_cache_load(const char *filepath, bool *outdated)
|
|||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tns_cache_write(thumb_t *t, const fileinfo_t *file, bool force)
|
void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
|
||||||
{
|
{
|
||||||
char *cfile, *dirend;
|
char *cfile, *dirend;
|
||||||
struct stat cstats, fstats;
|
struct stat cstats, fstats;
|
||||||
struct utimbuf times;
|
struct utimbuf times;
|
||||||
Imlib_Load_Error err = 0;
|
Imlib_Load_Error err = 0;
|
||||||
|
|
||||||
if (t == NULL || t->im == NULL)
|
if (im == NULL || filepath == NULL)
|
||||||
return;
|
return;
|
||||||
if (file == NULL || file->name == NULL || file->path == NULL)
|
if (stat(filepath, &fstats) < 0)
|
||||||
return;
|
|
||||||
if (stat(file->path, &fstats) < 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ((cfile = tns_cache_filepath(file->path)) != NULL) {
|
if ((cfile = tns_cache_filepath(filepath)) != NULL) {
|
||||||
if (force || stat(cfile, &cstats) < 0 ||
|
if (force || stat(cfile, &cstats) < 0 ||
|
||||||
cstats.st_mtime != fstats.st_mtime)
|
cstats.st_mtime != fstats.st_mtime)
|
||||||
{
|
{
|
||||||
@ -103,8 +100,8 @@ void tns_cache_write(thumb_t *t, const fileinfo_t *file, bool force)
|
|||||||
*dirend = '/';
|
*dirend = '/';
|
||||||
}
|
}
|
||||||
if (err == 0) {
|
if (err == 0) {
|
||||||
imlib_context_set_image(t->im);
|
imlib_context_set_image(im);
|
||||||
imlib_image_set_format("png");
|
imlib_image_set_format("jpg");
|
||||||
imlib_save_image_with_error_return(cfile, &err);
|
imlib_save_image_with_error_return(cfile, &err);
|
||||||
}
|
}
|
||||||
if (err == 0) {
|
if (err == 0) {
|
||||||
@ -173,6 +170,7 @@ void tns_init(tns_t *tns, const fileinfo_t *files, int cnt, int *sel, win_t *win
|
|||||||
tns->loadnext = tns->first = tns->end = tns->r_first = tns->r_end = 0;
|
tns->loadnext = tns->first = tns->end = tns->r_first = tns->r_end = 0;
|
||||||
tns->sel = sel;
|
tns->sel = sel;
|
||||||
tns->win = win;
|
tns->win = win;
|
||||||
|
tns->zl = 1;
|
||||||
tns->dirty = false;
|
tns->dirty = false;
|
||||||
|
|
||||||
if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') {
|
if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') {
|
||||||
@ -211,11 +209,34 @@ void tns_free(tns_t *tns)
|
|||||||
cache_dir = NULL;
|
cache_dir = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Imlib_Image tns_scale_down(Imlib_Image im, int dim)
|
||||||
|
{
|
||||||
|
int w, h;
|
||||||
|
float z, zw, zh;
|
||||||
|
|
||||||
|
imlib_context_set_image(im);
|
||||||
|
w = imlib_image_get_width();
|
||||||
|
h = imlib_image_get_height();
|
||||||
|
zw = (float) dim / (float) w;
|
||||||
|
zh = (float) dim / (float) h;
|
||||||
|
z = MIN(zw, zh);
|
||||||
|
z = MIN(z, 1.0);
|
||||||
|
|
||||||
|
if (z < 1.0) {
|
||||||
|
imlib_context_set_anti_alias(1);
|
||||||
|
im = imlib_create_cropped_scaled_image(0, 0, w, h, z * w, z * h);
|
||||||
|
if (im == NULL)
|
||||||
|
die("could not allocate memory");
|
||||||
|
imlib_free_image_and_decache();
|
||||||
|
}
|
||||||
|
return im;
|
||||||
|
}
|
||||||
|
|
||||||
bool tns_load(tns_t *tns, int n, bool force)
|
bool tns_load(tns_t *tns, int n, bool force)
|
||||||
{
|
{
|
||||||
int w, h;
|
int w, h;
|
||||||
bool cache_hit = false;
|
bool cache_hit = false;
|
||||||
float z, zw, zh;
|
float zw, zh;
|
||||||
thumb_t *t;
|
thumb_t *t;
|
||||||
Imlib_Image im = NULL;
|
Imlib_Image im = NULL;
|
||||||
const fileinfo_t *file;
|
const fileinfo_t *file;
|
||||||
@ -301,31 +322,20 @@ bool tns_load(tns_t *tns, int n, bool force)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imlib_context_set_image(im);
|
|
||||||
imlib_context_set_anti_alias(1);
|
|
||||||
|
|
||||||
|
if (!cache_hit) {
|
||||||
#if HAVE_LIBEXIF
|
#if HAVE_LIBEXIF
|
||||||
if (!cache_hit)
|
imlib_context_set_image(im);
|
||||||
exif_auto_orientate(file);
|
exif_auto_orientate(file);
|
||||||
#endif
|
#endif
|
||||||
|
im = tns_scale_down(im, thumb_size[ARRLEN(thumb_size)-1]);
|
||||||
|
tns_cache_write(im, file->path, true);
|
||||||
|
}
|
||||||
|
|
||||||
w = imlib_image_get_width();
|
t->im = tns_scale_down(im, thumb_size[tns->zl]);
|
||||||
h = imlib_image_get_height();
|
imlib_context_set_image(t->im);
|
||||||
zw = (float) THUMB_SIZE / (float) w;
|
t->w = imlib_image_get_width();
|
||||||
zh = (float) THUMB_SIZE / (float) h;
|
t->h = imlib_image_get_height();
|
||||||
z = MIN(zw, zh);
|
|
||||||
z = MIN(z, 1.0);
|
|
||||||
t->w = z * w;
|
|
||||||
t->h = z * h;
|
|
||||||
|
|
||||||
t->im = imlib_create_cropped_scaled_image(0, 0, w, h, t->w, t->h);
|
|
||||||
if (t->im == NULL)
|
|
||||||
die("could not allocate memory");
|
|
||||||
|
|
||||||
imlib_free_image_and_decache();
|
|
||||||
|
|
||||||
if (!cache_hit)
|
|
||||||
tns_cache_write(t, file, true);
|
|
||||||
|
|
||||||
tns->dirty = true;
|
tns->dirty = true;
|
||||||
return true;
|
return true;
|
||||||
@ -382,6 +392,7 @@ void tns_render(tns_t *tns)
|
|||||||
thumb_t *t;
|
thumb_t *t;
|
||||||
win_t *win;
|
win_t *win;
|
||||||
int i, cnt, r, x, y;
|
int i, cnt, r, x, y;
|
||||||
|
int thumb_dim;
|
||||||
|
|
||||||
if (tns == NULL || tns->thumbs == NULL || tns->win == NULL)
|
if (tns == NULL || tns->thumbs == NULL || tns->win == NULL)
|
||||||
return;
|
return;
|
||||||
@ -392,6 +403,7 @@ void tns_render(tns_t *tns)
|
|||||||
win_clear(win);
|
win_clear(win);
|
||||||
imlib_context_set_drawable(win->buf.pm);
|
imlib_context_set_drawable(win->buf.pm);
|
||||||
|
|
||||||
|
thumb_dim = thumb_size[tns->zl] + 10;
|
||||||
tns->cols = MAX(1, win->w / thumb_dim);
|
tns->cols = MAX(1, win->w / thumb_dim);
|
||||||
tns->rows = MAX(1, win->h / thumb_dim);
|
tns->rows = MAX(1, win->h / thumb_dim);
|
||||||
|
|
||||||
@ -422,8 +434,8 @@ void tns_render(tns_t *tns)
|
|||||||
for (i = tns->first; i < tns->end; i++) {
|
for (i = tns->first; i < tns->end; i++) {
|
||||||
t = &tns->thumbs[i];
|
t = &tns->thumbs[i];
|
||||||
if (t->im != NULL) {
|
if (t->im != NULL) {
|
||||||
t->x = x + (THUMB_SIZE - t->w) / 2;
|
t->x = x + (thumb_size[tns->zl] - t->w) / 2;
|
||||||
t->y = y + (THUMB_SIZE - t->h) / 2;
|
t->y = y + (thumb_size[tns->zl] - t->h) / 2;
|
||||||
imlib_context_set_image(t->im);
|
imlib_context_set_image(t->im);
|
||||||
imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h);
|
imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h);
|
||||||
if (tns->files[i].marked)
|
if (tns->files[i].marked)
|
||||||
@ -550,6 +562,26 @@ bool tns_scroll(tns_t *tns, direction_t dir, bool screen)
|
|||||||
return tns->first != old;
|
return tns->first != old;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool tns_zoom(tns_t *tns, int d)
|
||||||
|
{
|
||||||
|
int i, oldzl;
|
||||||
|
|
||||||
|
if (tns == NULL || tns->thumbs == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
oldzl = tns->zl;
|
||||||
|
tns->zl += -(d < 0) + (d > 0);
|
||||||
|
tns->zl = MAX(tns->zl, 0);
|
||||||
|
tns->zl = MIN(tns->zl, ARRLEN(thumb_size)-1);
|
||||||
|
|
||||||
|
if (tns->zl != oldzl) {
|
||||||
|
for (i = 0; i < tns->cnt; i++)
|
||||||
|
tns_unload(tns, i);
|
||||||
|
tns->dirty = true;
|
||||||
|
}
|
||||||
|
return tns->zl != oldzl;
|
||||||
|
}
|
||||||
|
|
||||||
int tns_translate(tns_t *tns, int x, int y)
|
int tns_translate(tns_t *tns, int x, int y)
|
||||||
{
|
{
|
||||||
int n;
|
int n;
|
||||||
@ -559,8 +591,8 @@ int tns_translate(tns_t *tns, int x, int y)
|
|||||||
if (x < tns->x || y < tns->y)
|
if (x < tns->x || y < tns->y)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
n = tns->first + (y - tns->y) / thumb_dim * tns->cols +
|
n = tns->first + (y - tns->y) / (thumb_size[tns->zl] + 10) * tns->cols +
|
||||||
(x - tns->x) / thumb_dim;
|
(x - tns->x) / (thumb_size[tns->zl] + 10);
|
||||||
if (n >= tns->cnt)
|
if (n >= tns->cnt)
|
||||||
n = -1;
|
n = -1;
|
||||||
|
|
||||||
|
3
thumbs.h
3
thumbs.h
@ -47,6 +47,7 @@ typedef struct {
|
|||||||
int y;
|
int y;
|
||||||
int cols;
|
int cols;
|
||||||
int rows;
|
int rows;
|
||||||
|
int zl;
|
||||||
|
|
||||||
bool dirty;
|
bool dirty;
|
||||||
} tns_t;
|
} tns_t;
|
||||||
@ -66,6 +67,8 @@ void tns_highlight(tns_t*, int, bool);
|
|||||||
bool tns_move_selection(tns_t*, direction_t, int);
|
bool tns_move_selection(tns_t*, direction_t, int);
|
||||||
bool tns_scroll(tns_t*, direction_t, bool);
|
bool tns_scroll(tns_t*, direction_t, bool);
|
||||||
|
|
||||||
|
bool tns_zoom(tns_t*, int);
|
||||||
|
|
||||||
int tns_translate(tns_t*, int, int);
|
int tns_translate(tns_t*, int, int);
|
||||||
|
|
||||||
#endif /* THUMBS_H */
|
#endif /* THUMBS_H */
|
||||||
|
Loading…
Reference in New Issue
Block a user