/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * VgaGames is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "canvas.h"

void init_canvas(void);
void dest_canvas(void);

struct vgi_canvas_init cvinit;

/* cvtypes.c */
extern void cvtypes_init(struct vgi_canvas_init *);
extern VG_BOOL cvtypes_readsection(struct vgi_canvas_init *, struct VG_Canvas *, struct vgi_canvas_item *, char *, const char *);

struct VG_Canvas * vg4_canvas_load_nolist(const char *, struct VG_Hash *);

static struct VG_Canvas * canvas_load(const char *, struct VG_Hash *);
static struct VG_Canvas * canvas_load_doit(const char *, struct VG_Hash *, VG_BOOL);
static VG_BOOL canvas_reload(struct VG_Canvas *, struct VG_Hash *);
static void canvas_destroy(struct VG_Canvas *);
static void canvas_destroyall(void);
static const char * canvas_getname(const struct VG_Canvas *);
static VG_BOOL canvas_exec(struct VG_Canvas *, const struct VG_Position *, const char **);
static struct VG_Position canvas_subcanvas(const struct VG_Canvas *, const struct VG_ImagecopyAttrPixel *);
static void canvas_set_mouse(const char *, const char *, int);
static void canvas_set_activated(struct VG_Canvas *, const char *);
static void canvas_disable(struct VG_Canvas *, const char *, VG_BOOL);
static VG_BOOL canvas_is_disabled(const struct VG_Canvas *, const char *);
static void canvas_dump(FILE *);

/* arrows */
static struct {
  const char *bdir;
  struct vgi_canvas_img *cvim;
} s_arrow[4] = {
  { "up", NULL },
  { "down", NULL },
  { "left", NULL },
  { "right", NULL },
};

static VG_BOOL valid_section(const char *, const char *);
static VG_BOOL is_new_section(char *, size_t *, long *, VG_BOOL);
static VG_BOOL read_section(struct VG_Canvas *, const char *, FILE *, int);
static void warp_mouse(struct VG_Canvas *, int, int, int);


/* set functions */
void
init_canvas(void)
{
  vg4data.lists.canvas.list = SML3_calloc(1, sizeof(*vg4data.lists.canvas.list));  /* first list-element is unused */
  vg4data.lists.canvas.list->prev = vg4data.lists.canvas.list->next = NULL;
  vg4data.lists.canvas.mouse.img_dfl.type = VGI_IMGCNT_NONE;
  vg4data.lists.canvas.mouse.img_act.type = VGI_IMGCNT_NONE;
  cvtypes_init(&cvinit);
  vg4->canvas->load = canvas_load;
  vg4->canvas->reload = canvas_reload;
  vg4->canvas->destroy = canvas_destroy;
  vg4->canvas->destroyall = canvas_destroyall;
  vg4->canvas->getname = canvas_getname;
  vg4->canvas->exec = canvas_exec;
  vg4->canvas->subcanvas = canvas_subcanvas;
  vg4->canvas->set_mouse = canvas_set_mouse;
  vg4->canvas->set_activated = canvas_set_activated;
  vg4->canvas->disable = canvas_disable;
  vg4->canvas->is_disabled = canvas_is_disabled;
  vg4->canvas->dump = canvas_dump;
} /* Ende init_canvas */

void
dest_canvas(void)
{
  vg4->canvas->destroyall();
  if (cvinit.type != NULL) { free(cvinit.type); }
  memset(&cvinit, 0, sizeof(cvinit));
  free(vg4data.lists.canvas.list);  /* first list-element is unused */
  vg4data.lists.canvas.list = NULL;
  vg4_intern_free_imgcnt(&vg4data.lists.canvas.mouse.img_dfl);
  vg4_intern_free_imgcnt(&vg4data.lists.canvas.mouse.img_act);
} /* Ende dest_canvas */


/* vg4_canvas_load_nolist: like canvas_load(), but without inserting into list */
struct VG_Canvas *
vg4_canvas_load_nolist(const char *filename, struct VG_Hash *hvar)
{
  return canvas_load_doit(filename, hvar, VG_FALSE);
} /* Ende vg4_canvas_load_nolist */


/* check if section is valid */
static VG_BOOL
valid_section(const char *pt1, const char *pt2)
{
  if (pt1 == NULL || pt2 == NULL) { return VG_FALSE; }

  if (*pt1 != '[' || *pt2 != ']') { return VG_FALSE; }

  pt1++;
  if (pt2 <= pt1) { return VG_FALSE; }

  if (strspn(pt1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != (size_t)(pt2 - pt1)) { return VG_FALSE; }

  return VG_TRUE;
} /* Ende valid_section */


/* check if new section header has been read */
static VG_BOOL
is_new_section(char *gmptr, size_t *gpos, long *fpos, VG_BOOL withcomment)
{
  char *pt1, *pt2;
  VG_BOOL scnp;

  if (gmptr == NULL || gpos == NULL || fpos == NULL) { return VG_FALSE; }

  pt1 = pt2 = &gmptr[*gpos - 1];
  if (*pt1 != '\n') { return VG_FALSE; }

  /* search for previous newline and cut off spaces (and comment) from last line */
  *fpos = -1L;
  scnp = VG_TRUE;
  while (--pt1 >= gmptr) {
    if (*pt1 == '\n') { break; }
    (*fpos)--;
    if (scnp) {
      if (strchr(VGI_SPACE, (int)*pt1) != NULL) { pt2 = pt1; } else { scnp = VG_FALSE; }
    }
    if (withcomment && *pt1 == '#') { pt2 = pt1; scnp = VG_TRUE; }
  }
  pt1++;
  pt2[0] = '\n';
  pt2[1] = '\0';
  *gpos = (size_t)(pt2 + 1 - gmptr);
  pt2--;

  /* new section? */
  if (valid_section(pt1, pt2)) {
    *pt1 = '\0';
    *gpos = (size_t)(pt1 - gmptr);
    return VG_TRUE;
  }
  *fpos = 0L;

  return VG_FALSE;
} /* Ende is_new_section */


/* read section of canvas file */
static VG_BOOL
read_section(struct VG_Canvas *cvas, const char *sectname, FILE *ffp, int preproz)
{
  struct SML3_gummi gm1 = SML3_GUM_INITIALIZER;
  struct SML3_gummi gm2 = SML3_GUM_INITIALIZER;
  size_t gpos = 0;
  ssize_t glen;
  char *gptr, *pval;
  long fpos;
  char ename[64], bprefix[32], bval[256], rfile[VGI_PATHSIZE];
  VG_BOOL withcomment;

  if (cvas == NULL || sectname == NULL || ffp == NULL) { outerr("invalid parameters"); return VG_FALSE; }

  /* read contents up to next section */
  if (strcmp(sectname, "[MAIN]") == 0) { withcomment = VG_FALSE; } else { withcomment = VG_TRUE; }
  while ((glen = SML3_gumfget(&gm1, gpos, ffp)) > 0) {
    gpos += glen;
    if (is_new_section(SML3_gumgetval(&gm1), &gpos, &fpos, withcomment)) {
      fseek(ffp, fpos, SEEK_CUR);
      break;
    }
  }
  gptr = SML3_gumgetval(&gm1);
  vg4_intern_skip_ws((const char **)&gptr, gptr + strlen(gptr));
  SML3_schnipp(gptr, VGI_WSPACE, 2);

  /* sections */

  if (strcmp(sectname, "[VAR-DEFAULT]") == 0) {
    if (preproz == 1) {
      char *nptr = gptr;
      while (canvas_get_lineval(nptr, "var", ename, sizeof(ename), NULL, 0, bval, sizeof(bval), &nptr)) {
        if (!vg4->hash->isset(cvas->cve.hvar, ename)) { vg4->hash->setstr(cvas->cve.hvar, ename, bval); }
      }
    }

  } else if (strcmp(sectname, "[FONT]") == 0) {
    if (preproz == 2) {
      vg4_intern_str_with_var(&gm2, gptr, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      vg4->misc->strcpy(cvas->cve.dflfont, sizeof(cvas->cve.dflfont), pval);
    }

  } else if (strcmp(sectname, "[ARROWS]") == 0) {
    if (preproz == 2) {
      int a1;
      char bkey[16];
      s_arrow[0].cvim = &cvas->cve.arrow.user.up;
      s_arrow[1].cvim = &cvas->cve.arrow.user.down;
      s_arrow[2].cvim = &cvas->cve.arrow.user.left;
      s_arrow[3].cvim = &cvas->cve.arrow.user.right;
      for (a1 = 0; a1 < 4; a1++) {
        snprintf(bkey, sizeof(bkey), "%s-dfl", s_arrow[a1].bdir);
        if (canvas_get_lineval(gptr, bkey, NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
          if (*bval != '/') {
            vg4->misc->strccat(rfile, sizeof(rfile), cvas->cve.dirname, "/", bval, NULL);
          } else {
            vg4->misc->strcpy(rfile, sizeof(rfile), bval + 1);
          }
          vg4_intern_fpath_with_var(rfile, sizeof(rfile), cvas->cve.hvar);
          s_arrow[a1].cvim->img_dfl = vg4_image_load_nolist(rfile);
          if (s_arrow[a1].cvim->img_dfl != NULL) { cvas->cve.arrow.user_set = VG_TRUE; }
        }
        snprintf(bkey, sizeof(bkey), "%s-act", s_arrow[a1].bdir);
        if (canvas_get_lineval(gptr, bkey, NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
          if (*bval != '/') {
            vg4->misc->strccat(rfile, sizeof(rfile), cvas->cve.dirname, "/", bval, NULL);
          } else {
            vg4->misc->strcpy(rfile, sizeof(rfile), bval + 1);
          }
          vg4_intern_fpath_with_var(rfile, sizeof(rfile), cvas->cve.hvar);
          s_arrow[a1].cvim->img_act = vg4_image_load_nolist(rfile);
          if (s_arrow[a1].cvim->img_act != NULL) {
            if (s_arrow[a1].cvim->img_dfl == NULL) { s_arrow[a1].cvim->img_dfl = vg4_image_load_nolist(rfile); }
            cvas->cve.arrow.user_set = VG_TRUE;
          }
        }
        snprintf(bkey, sizeof(bkey), "%s-sel", s_arrow[a1].bdir);
        if (canvas_get_lineval(gptr, bkey, NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
          if (*bval != '/') {
            vg4->misc->strccat(rfile, sizeof(rfile), cvas->cve.dirname, "/", bval, NULL);
          } else {
            vg4->misc->strcpy(rfile, sizeof(rfile), bval + 1);
          }
          vg4_intern_fpath_with_var(rfile, sizeof(rfile), cvas->cve.hvar);
          s_arrow[a1].cvim->img_sel = vg4_image_load_nolist(rfile);
          if (s_arrow[a1].cvim->img_sel != NULL) {
            if (s_arrow[a1].cvim->img_dfl == NULL) { s_arrow[a1].cvim->img_dfl = vg4_image_load_nolist(rfile); }
            cvas->cve.arrow.user_set = VG_TRUE;
          }
        }
        snprintf(bkey, sizeof(bkey), "%s-end", s_arrow[a1].bdir);
        if (canvas_get_lineval(gptr, bkey, NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
          if (*bval != '/') {
            vg4->misc->strccat(rfile, sizeof(rfile), cvas->cve.dirname, "/", bval, NULL);
          } else {
            vg4->misc->strcpy(rfile, sizeof(rfile), bval + 1);
          }
          vg4_intern_fpath_with_var(rfile, sizeof(rfile), cvas->cve.hvar);
          s_arrow[a1].cvim->img_end = vg4_image_load_nolist(rfile);
          if (s_arrow[a1].cvim->img_end != NULL) {
            if (s_arrow[a1].cvim->img_dfl == NULL) { s_arrow[a1].cvim->img_dfl = vg4_image_load_nolist(rfile); }
            cvas->cve.arrow.user_set = VG_TRUE;
          }
        }
      }
    }

  } else if (strcmp(sectname, "[MAIN]") == 0) {
    if (cvas->cve.main.img != NULL) { free(cvas->cve.main.img); }
    if (cvas->cve.main.htags != NULL) { vg4->hash->destroy(cvas->cve.main.htags); }
    { char afont[64];
      vg4->font->getdefault(afont, sizeof(afont));
      if (*cvas->cve.dflfont != '\0') { vg4->font->setdefault(cvas->cve.dflfont); }
      cvas->cve.main.img = vg4_font_totext_nolist(gptr, NULL, cvas->cve.dirname, cvas->cve.hvar, &cvas->cve.main.htags);
      vg4->font->setdefault(afont);
    }
    if (cvas->cve.main.img == NULL) { SML3_gumdest(&gm1); SML3_gumdest(&gm2); return VG_FALSE; }

  } else if (strcmp(sectname, "[MOUSE]") == 0) {
    if (canvas_get_lineval(gptr, "img-dfl", NULL, 0, bprefix, sizeof(bprefix), bval, sizeof(bval), NULL)) {
      canvas_load_image(cvas, bprefix, bval, &cvas->cve.mouse.img_dfl);
    }
    if (canvas_get_lineval(gptr, "img-act", NULL, 0, bprefix, sizeof(bprefix), bval, sizeof(bval), NULL)) {
      canvas_load_image(cvas, bprefix, bval, &cvas->cve.mouse.img_act);
    }
    if (canvas_get_lineval(gptr, "point-centered", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      DETECT_BOOLEAN(pval, cvas->cve.mouse.point_centered);
    }
    if (canvas_get_lineval(gptr, "disable", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      DETECT_BOOLEAN(pval, cvas->cve.mouse.show);
      cvas->cve.mouse.show = (cvas->cve.mouse.show == VG_TRUE ? VG_FALSE : VG_TRUE);
    }

  } else if (strcmp(sectname, "[ACTIVATED]") == 0) {
    vg4_intern_str_with_var(&gm2, gptr, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
    vg4->misc->strcpy(cvas->cve.aname, sizeof(cvas->cve.aname), pval);

  } else if (strcmp(sectname, "[SPRITE]") == 0) {
    int ielm = cvas->cve.sprites.max++;
    if (ielm == 0) {
      cvas->cve.sprites.e = SML3_malloc(sizeof(*cvas->cve.sprites.e));
    } else {
      cvas->cve.sprites.e = SML3_realloc(cvas->cve.sprites.e, (ielm + 1) * sizeof(*cvas->cve.sprites.e));
    }
    memset(&cvas->cve.sprites.e[ielm], 0, sizeof(*cvas->cve.sprites.e));
    if (canvas_get_lineval(gptr, "position-itag", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      vg4->misc->strcpy(cvas->cve.sprites.e[ielm].itag, sizeof(cvas->cve.sprites.e[ielm].itag), pval);
    }
    if (canvas_get_lineval(gptr, "position-rect", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      cvas->cve.sprites.e[ielm].limrect = vg4_intern_read_rect(pval);
    }
    if (canvas_get_lineval(gptr, "textbox", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      vg4->misc->strcpy(cvas->cve.sprites.e[ielm].textbox, sizeof(cvas->cve.sprites.e[ielm].textbox), pval);
    }
    if (canvas_get_lineval(gptr, "sprite", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      if (*bval != '/') {
        vg4->misc->strccat(rfile, sizeof(rfile), cvas->cve.dirname, "/", bval, NULL);
      } else {
        vg4->misc->strcpy(rfile, sizeof(rfile), bval + 1);
      }
      vg4_intern_fpath_with_var(rfile, sizeof(rfile), cvas->cve.hvar);
      cvas->cve.sprites.e[ielm].sprt = vg4_sprite_load_nolist(rfile);
    }
    if (cvas->cve.sprites.e[ielm].sprt == NULL) {  /* remove new entry */
      if (--cvas->cve.sprites.max == 0) { free(cvas->cve.sprites.e); cvas->cve.sprites.e = NULL; }
    }

  } else if (strlen(sectname) > 4 && strncmp(sectname, "[CV-", 4) == 0) {
    int ielm = cvas->cve.canvas.max++;

    if (ielm == 0) {
      cvas->cve.canvas.e = SML3_malloc(sizeof(*cvas->cve.canvas.e));
    } else {
      cvas->cve.canvas.e = SML3_realloc(cvas->cve.canvas.e, (ielm + 1) * sizeof(*cvas->cve.canvas.e));
    }
    memset(&cvas->cve.canvas.e[ielm], 0, sizeof(*cvas->cve.canvas.e));

    if (canvas_get_lineval(gptr, "name", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      vg4->misc->strcpy(cvas->cve.canvas.e[ielm].name, sizeof(cvas->cve.canvas.e[ielm].name), pval);
    }
    if (canvas_get_lineval(gptr, "position-itag", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      vg4->misc->strcpy(cvas->cve.canvas.e[ielm].itag, sizeof(cvas->cve.canvas.e[ielm].itag), pval);
    }
    if (canvas_get_lineval(gptr, "position-rect", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      cvas->cve.canvas.e[ielm].limrect = vg4_intern_read_rect(pval);
    }
    if (canvas_get_lineval(gptr, "disable", NULL, 0, NULL, 0, bval, sizeof(bval), NULL)) {
      vg4_intern_str_with_var(&gm2, bval, cvas->cve.hvar, NULL); pval = SML3_gumgetval(&gm2);
      DETECT_BOOLEAN(pval, cvas->cve.canvas.e[ielm].isdisabled);
    }

    cvas->cve.canvas.e[ielm].isselectable = VG_TRUE;

    if (*cvas->cve.canvas.e[ielm].name == '\0'
        || (*cvas->cve.canvas.e[ielm].itag == '\0' && IS_RECT_EMPTY(&cvas->cve.canvas.e[ielm].limrect))
       ) {  /* remove new entry */
      pwarn("section \"%s\": no name or position\n", sectname);
      SML3_gumdest(&gm1);
      SML3_gumdest(&gm2);
      return VG_FALSE;
    }

    /* read section */
    if (!cvtypes_readsection(&cvinit, cvas, &cvas->cve.canvas.e[ielm], gptr, sectname)) {
      SML3_gumdest(&gm1);
      SML3_gumdest(&gm2);
      return VG_FALSE;
    }

  } else {
    pwarn("section \"%s\" unknown\n", sectname);
    SML3_gumdest(&gm1);
    SML3_gumdest(&gm2);
    return VG_FALSE;
  }

  SML3_gumdest(&gm1);
  SML3_gumdest(&gm2);

  return VG_TRUE;
} /* Ende read_section */


/* warp mouse */
static void
warp_mouse(struct VG_Canvas *cvas, int ielm, int xadd, int yadd)
{
  int xm, ym;

  if (cvas == NULL || !cvas->cve.mouse.show) { return; }
  if (ielm < 0 || ielm >= cvas->cve.canvas.max) { return; }

  if (cvinit.type[cvas->cve.canvas.e[ielm].type].warp_mouse != NULL) {
    cvinit.type[cvas->cve.canvas.e[ielm].type].warp_mouse(cvas, &cvas->cve.canvas.e[ielm], xadd, yadd, &xm, &ym);
  } else {
    xm = xadd + cvas->cve.canvas.e[ielm].rect.x + cvas->cve.canvas.e[ielm].rect.w / 2;
    ym = yadd + cvas->cve.canvas.e[ielm].rect.y + cvas->cve.canvas.e[ielm].rect.h / 2;
  }

  vg4->input->mouse_warp(xm, ym);
} /* Ende warp_mouse */


/* canvas_load:
 * load canvas from file
 * @param filename  filename
 * @param hvar      hash with variable-values for text control-commands "var" and for "$(<varname>)", or NULL
 *                    hash-format:
 *                     - key:   variable name
 *                     - value: variable value
 *                  hash will be copied, therefore may be freed after function call
 * @return  canvas or NULL = error
 *
 * canvas-file:
 *   comment: after '#'
 *
 *   The filename of images, text files or sprites to be loaded in the canvas-file
 *   are searched relatively to the canvas-file.
 *   If a filename of a file to be loaded begins with a '/',
 *   it is searched from the current directory.
 *
 *   The filename of images, text files or sprites to be loaded may contain variables
 *     in the format: "$(<varname>)",
 *     a specific automatic variable "LANG" contains the (fallback) locale,
 *     e.g.: img:mypath_$(LANG)/myimage.bmp
 *
 *   The other parameters except text (according to vg4->font->totext())
 *   may also contain "$(<varname>)".
 *
 *   sections:
 *    - main text (according to vg4->font->totext())
 *      [MAIN]
 *      <text lines>
 *    - (optional:) mouse image
 *      [MOUSE]
 *      disable: <whether is disabled: 1 = yes, 0 = no, or missing = enabled>
 *      img-dfl: [img:]<default image file> or txt:<default text file> or sprt:<default sprite file>
 *      img-act: [img:]<activated image file> or txt:<activated text file> or sprt:<activated sprite file>
 *      point-centered: which pixel of mouse-image points: 1 = center of mouse-image, 0 = upper left pixel of mouse-image, (default: 0)
 *    - (optional:) arrow images
 *      [ARROWS]
 *      <direction>-dfl: image-filename for default arrow, where <direction> is one of: up, down, left, right
 *      <direction>-act: image-filename for activated arrow, where <direction> is one of: up, down, left, right
 *      <direction>-sel: image-filename for selected arrow, where <direction> is one of: up, down, left, right
 *      <direction>-end: image-filename for ended arrow, where <direction> is one of: up, down, left, right
 *    - (optional, repeated:) sprites on top of the main text
 *      [SPRITE]
 *      position-itag: (position): <box-tag of an element in the main text>
 *      position-rect: (position): <rectangle (<x>+<w>,<y>+<h>) within the box-tag or the main text>
 *      textbox: <item-name of text-box the sprite belongs to, or missing = belongs to main text>
 *      sprite: <sprite file>
 *    - (recommended:) default font
 *      [FONT]
 *      <line: fontname>
 *    - (optional:) default activated canvas-item
 *      [ACTIVATED]
 *      <line: name of a selectable canvas-item>
 *    - (optional:) default values of variables
 *      [VAR-DEFAULT]
 *      var.<name>: (repeated for each variable): <default value>
 *    - repeated according to canvas-types
 *        [CV-*]  # see cv_*.c
 */
static struct VG_Canvas *
canvas_load(const char *filename, struct VG_Hash *hvar)
{
  return canvas_load_doit(filename, hvar, VG_TRUE);
} /* Ende canvas_load */


/* canvas_load_doit: do action */
static struct VG_Canvas *
canvas_load_doit(const char *filename, struct VG_Hash *hvar, VG_BOOL intolist)
{
  struct VG_Canvas *cvas, **cvasp;
  FILE *ffp;
  const char *kptr;
  char buf[1024], *pt1;
  size_t slen;
  int ielm;

  if (filename == NULL || *filename == '\0') { outerr("loading canvas: no filename"); return NULL; }

  if ((ffp = fopen(filename, "r")) == NULL) { outerr("loading canvas: fopen(%s): %s", filename, strerror(errno)); return NULL; }

  /* create canvas */
  cvas = SML3_calloc(1, sizeof(*cvas));
  cvas->prev = cvas->next = NULL;
  cvas->cve.fname = SML3_strdup(filename);
  kptr = strrchr(filename, '/');
  if (kptr != NULL) {
    snprintf(cvas->cve.dirname, sizeof(cvas->cve.dirname), "%.*s", (int)(kptr - filename), filename);
  } else {
    snprintf(cvas->cve.dirname, sizeof(cvas->cve.dirname), ".");
  }
  cvas->cve.intolist = intolist;
  cvas->cve.hvar = vg4_hash_clone_nolist(hvar);
  cvas->cve.w_img = NULL;
  *cvas->cve.aname = '\0';
  cvas->cve.mouse.show = VG_TRUE;
  cvas->cve.main.img = NULL;
  cvas->cve.main.htags = NULL;
  *cvas->cve.dflfont = '\0';

  if ((kptr = strstr(cvas->cve.fname, "/" VGI_SIZE_LOW "/")) != NULL) {
    cvas->cve.ishigh = VG_FALSE;
  } else if ((kptr = strstr(cvas->cve.fname, "/" VGI_SIZE_HIGH "/")) != NULL) {
    cvas->cve.ishigh = VG_TRUE;
  } else {
    int wsize;
    vg4->window->getparameters(&wsize, NULL);
    if (wsize == VG_WINDOW_SIZE_LOW) { cvas->cve.ishigh = VG_FALSE; } else { cvas->cve.ishigh = VG_TRUE; }
  }

  /* load arrow-images */
  { const char *prefix = vg4_get_prefix(NULL);
    char imgfile[VGI_PATHSIZE];
    int a1;

    s_arrow[0].cvim = &cvas->cve.arrow.low.up;
    s_arrow[1].cvim = &cvas->cve.arrow.low.down;
    s_arrow[2].cvim = &cvas->cve.arrow.low.left;
    s_arrow[3].cvim = &cvas->cve.arrow.low.right;
    for (a1 = 0; a1 < 4; a1++) {
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-dfl.bmp", prefix, VGI_SIZE_LOW, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_dfl = vg4_image_load_nolist(imgfile);
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-act.bmp", prefix, VGI_SIZE_LOW, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_act = vg4_image_load_nolist(imgfile);
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-sel.bmp", prefix, VGI_SIZE_LOW, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_sel = vg4_image_load_nolist(imgfile);
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-end.bmp", prefix, VGI_SIZE_LOW, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_end = vg4_image_load_nolist(imgfile);
    }

    s_arrow[0].cvim = &cvas->cve.arrow.high.up;
    s_arrow[1].cvim = &cvas->cve.arrow.high.down;
    s_arrow[2].cvim = &cvas->cve.arrow.high.left;
    s_arrow[3].cvim = &cvas->cve.arrow.high.right;
    for (a1 = 0; a1 < 4; a1++) {
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-dfl.bmp", prefix, VGI_SIZE_HIGH, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_dfl = vg4_image_load_nolist(imgfile);
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-act.bmp", prefix, VGI_SIZE_HIGH, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_act = vg4_image_load_nolist(imgfile);
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-sel.bmp", prefix, VGI_SIZE_HIGH, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_sel = vg4_image_load_nolist(imgfile);
      snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/arrow_%s-end.bmp", prefix, VGI_SIZE_HIGH, s_arrow[a1].bdir);
      s_arrow[a1].cvim->img_end = vg4_image_load_nolist(imgfile);
    }

    memset(&cvas->cve.arrow.user, 0, sizeof(cvas->cve.arrow.user));
  }

  /* load hover sound if existing */
  cvas->cve.audc_hover = 0;
  snprintf(buf, sizeof(buf), "%s/hover.wav", cvas->cve.dirname);
  if (access(buf, R_OK) == 0) {
    cvas->cve.audc_hover = vg4_audio_load_nolist(buf, 100, VG_AUDIO_VOLUME_SOUND | VG_AUDIO_SYSTEM);
  }

  /* read in */

  /* preprocessor */
  while (fgets(buf, sizeof(buf), ffp) != NULL) {
    if ((pt1 = strchr(buf, '#')) != NULL) { *pt1 = '\0'; }
    slen = SML3_schnipp(buf, VGI_WSPACE, 2);
    if (strcmp(buf, "[VAR-DEFAULT]") == 0
        || strcmp(buf, "[FONT]") == 0
        || strcmp(buf, "[ARROWS]") == 0) {
      if (!read_section(cvas, buf, ffp, 1)) {
        fclose(ffp);
        vg4->canvas->destroy(cvas);
        return NULL;
      }
    }
  }
  rewind(ffp);

  /* preprocessor */
  while (fgets(buf, sizeof(buf), ffp) != NULL) {
    if ((pt1 = strchr(buf, '#')) != NULL) { *pt1 = '\0'; }
    slen = SML3_schnipp(buf, VGI_WSPACE, 2);
    if (strcmp(buf, "[VAR-DEFAULT]") == 0
        || strcmp(buf, "[FONT]") == 0
        || strcmp(buf, "[ARROWS]") == 0) {
      if (!read_section(cvas, buf, ffp, 2)) {
        fclose(ffp);
        vg4->canvas->destroy(cvas);
        return NULL;
      }
    }
  }
  rewind(ffp);

  /* read remaining */
  while (fgets(buf, sizeof(buf), ffp) != NULL) {
    if ((pt1 = strchr(buf, '#')) != NULL) { *pt1 = '\0'; }
    slen = SML3_schnipp(buf, VGI_WSPACE, 2);
    if (valid_section(buf, buf + slen - 1)) {  /* new section */
      if (!read_section(cvas, buf, ffp, 0)) {
        fclose(ffp);
        vg4->canvas->destroy(cvas);
        return NULL;
      }
    }
  }
  fclose(ffp);

  /* make rectangles of items of canvas-type absolute */
  for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
    canvas_calc_rect(cvas, ielm, VG_FALSE);
  }

  /* make rectangles of sprites absolute */
  for (ielm = 0; ielm < cvas->cve.sprites.max; ielm++) {
    canvas_calc_rect(cvas, ielm, VG_TRUE);
  }

  /* insert canvas into list */
  if (intolist) {
    for (cvasp = &vg4data.lists.canvas.list; *cvasp != NULL; cvasp = &(*cvasp)->next) { cvas->prev = *cvasp; }
    *cvasp = cvas;
  }

  return cvas;
} /* Ende canvas_load_doit */


/* canvas_reload:
 * reload canvas with actual variable-values
 * @param cvas  canvas
 * @param hvar  hash with variable-values (see canvas_load())
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
canvas_reload(struct VG_Canvas *cvas, struct VG_Hash *hvar)
{
  struct VG_Canvas *cvas_tmp, **cvasp;
  char *fname;
  struct VG_Image *w_img;
  VG_BOOL intolist;

  if (cvas == NULL) { outerr("reloading canvas: no canvas"); return VG_FALSE; }
  if (cvas->cve.fname == NULL) { outerr("reloading canvas: no canvas filename found"); return VG_FALSE; }

  fname = SML3_strdup(cvas->cve.fname);
  w_img = cvas->cve.w_img;
  cvas->cve.w_img = NULL;
  intolist = cvas->cve.intolist;

  cvas_tmp = SML3_calloc(1, sizeof(*cvas_tmp));
  memcpy(cvas_tmp, cvas, sizeof(*cvas_tmp));
  memset(cvas, 0, sizeof(*cvas));

  vg4->canvas->destroy(cvas_tmp);
  cvas_tmp = canvas_load_doit(fname, hvar, VG_FALSE);
  free(fname);
  if (cvas_tmp == NULL) { return VG_FALSE; }

  memcpy(cvas, cvas_tmp, sizeof(*cvas));
  free(cvas_tmp);

  cvas->cve.w_img = w_img;
  cvas->cve.intolist = intolist;

  if (intolist) {
    for (cvasp = &vg4data.lists.canvas.list; *cvasp != NULL; cvasp = &(*cvasp)->next) { cvas->prev = *cvasp; }
    *cvasp = cvas;
  }

  return VG_TRUE;
} /* Ende canvas_reload */


/* canvas_destroy:
 * destroy canvas
 * @param cvas  canvas
 */
static void
canvas_destroy(struct VG_Canvas *cvas)
{
  int ielm, a1;

  if (cvas == NULL) { return; }

  if (cvas->cve.fname != NULL) { free(cvas->cve.fname); }
  if (cvas->cve.audc_hover > 0) { vg4->audio->unload(cvas->cve.audc_hover); }
  vg4->hash->destroy(cvas->cve.hvar);

  s_arrow[0].cvim = &cvas->cve.arrow.low.up;
  s_arrow[1].cvim = &cvas->cve.arrow.low.down;
  s_arrow[2].cvim = &cvas->cve.arrow.low.left;
  s_arrow[3].cvim = &cvas->cve.arrow.low.right;
  for (a1 = 0; a1 < 4; a1++) {
    if (s_arrow[a1].cvim->img_dfl != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_dfl); }
    if (s_arrow[a1].cvim->img_act != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_act); }
    if (s_arrow[a1].cvim->img_sel != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_sel); }
    if (s_arrow[a1].cvim->img_end != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_end); }
  }

  s_arrow[0].cvim = &cvas->cve.arrow.high.up;
  s_arrow[1].cvim = &cvas->cve.arrow.high.down;
  s_arrow[2].cvim = &cvas->cve.arrow.high.left;
  s_arrow[3].cvim = &cvas->cve.arrow.high.right;
  for (a1 = 0; a1 < 4; a1++) {
    if (s_arrow[a1].cvim->img_dfl != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_dfl); }
    if (s_arrow[a1].cvim->img_act != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_act); }
    if (s_arrow[a1].cvim->img_sel != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_sel); }
    if (s_arrow[a1].cvim->img_end != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_end); }
  }

  s_arrow[0].cvim = &cvas->cve.arrow.user.up;
  s_arrow[1].cvim = &cvas->cve.arrow.user.down;
  s_arrow[2].cvim = &cvas->cve.arrow.user.left;
  s_arrow[3].cvim = &cvas->cve.arrow.user.right;
  for (a1 = 0; a1 < 4; a1++) {
    if (s_arrow[a1].cvim->img_dfl != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_dfl); }
    if (s_arrow[a1].cvim->img_act != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_act); }
    if (s_arrow[a1].cvim->img_sel != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_sel); }
    if (s_arrow[a1].cvim->img_end != NULL) { vg4->image->destroy(s_arrow[a1].cvim->img_end); }
  }

  if (cvas->cve.w_img != NULL) { vg4->image->destroy(cvas->cve.w_img); }

  if (cvas->cve.main.img != NULL) { vg4->image->destroy(cvas->cve.main.img); }
  if (cvas->cve.main.htags != NULL) { vg4->hash->destroy(cvas->cve.main.htags); }
  vg4_intern_free_imgcnt(&cvas->cve.mouse.img_dfl);
  vg4_intern_free_imgcnt(&cvas->cve.mouse.img_act);

  for (ielm = 0; ielm < cvas->cve.sprites.max; ielm++) {
    vg4->sprite->destroy(cvas->cve.sprites.e[ielm].sprt);
    if (cvas->cve.sprites.e[ielm].rimg != NULL) { vg4->image->destroy(cvas->cve.sprites.e[ielm].rimg); }
  }
  if (cvas->cve.sprites.max > 0) { free(cvas->cve.sprites.e); }

  for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
    if (cvas->cve.canvas.e[ielm].rimg != NULL) { vg4->image->destroy(cvas->cve.canvas.e[ielm].rimg); }
    if (cvinit.type[cvas->cve.canvas.e[ielm].type].destroy != NULL) {
      cvinit.type[cvas->cve.canvas.e[ielm].type].destroy(&cvas->cve.canvas.e[ielm]);
    }
  }
  if (cvas->cve.canvas.max > 0) { free(cvas->cve.canvas.e); }

  if (cvas->prev != NULL) { cvas->prev->next = cvas->next; }
  if (cvas->next != NULL) { cvas->next->prev = cvas->prev; }

  free(cvas);
} /* Ende canvas_destroy */


/* canvas_destroyall:
 * destroy all canvasses
 */
static void
canvas_destroyall(void)
{
  struct VG_Canvas *cvas0, *cvas1;

  if (vg4data.lists.canvas.list == NULL) { return; }

  for (cvas0 = vg4data.lists.canvas.list->next; cvas0 != NULL; cvas0 = cvas1) {
    if (cvas0->prev != NULL) { cvas0->prev->next = cvas0->next; }
    if (cvas0->next != NULL) { cvas0->next->prev = cvas0->prev; }
    cvas1 = cvas0->next;
    cvas0->prev = cvas0->next = NULL;
    vg4->canvas->destroy(cvas0);
  }
  vg4data.lists.canvas.list->prev = vg4data.lists.canvas.list->next = NULL;
} /* Ende canvas_destroyall */


/* canvas_getname:
 * get filename of the canvas
 * @param cvas  canvas
 * @return  filename
 */
static const char *
canvas_getname(const struct VG_Canvas *cvas)
{
  if (cvas == NULL || cvas->cve.fname == NULL) { return ""; }

  return cvas->cve.fname;
} /* Ende canvas_getname */


/* canvas_exec:
 * execute canvas
 * @param cvas     canvas
 * @param posdst   canvas-position on window, or NULL = centered
 * @param selname  for returning name of selected canvas-item:
 *                  - NULL: cancel
 *                  - empty: return OK
 *                  - not empty: name of selected canvas-item
 * @return  VG_TRUE = OK or VG_FALSE = exit-request
 */
static VG_BOOL
canvas_exec(struct VG_Canvas *cvas, const struct VG_Position *posdst, const char **selname)
{
  int keyrefs[CV_KEY_MAXENUM], kidx;
  struct vgi_canvas_data cvdata;
  VG_BOOL retw, bact;
  int ielm, iactv;
  int winw, winh, imgw, imgh;
  struct VG_Image *wclone, *imgp;
  struct VG_ImagecopyAttr iattr;
  struct VG_Position posdstb, posb;
  struct VG_ImagecopyAttrPixel wattr_pixel;

  if (selname == NULL) { return VG_TRUE; }
  *selname = NULL;
  if (cvas == NULL) { return VG_TRUE; }

  if (cvas->cve.w_img != NULL) { vg4->image->destroy(cvas->cve.w_img); cvas->cve.w_img = NULL; }

  wattr_pixel = vg4->window->getattr();
  vg4->window->setattr(NULL);

  wclone = vg4_window_clone_nolist(NULL, NULL);

  memset(&cvdata, 0, sizeof(cvdata));
  cvdata.cvas = cvas;

  /* calculate position-values to add for drawing image-elements */

  vg4->window->getsize(&winw, &winh);
  if (cvas->cve.main.img != NULL) {
    vg4->image->getsize(cvas->cve.main.img, NULL, &imgw, &imgh);
  } else {
    imgw = winw;
    imgh = winh;
  }

  if (posdst == NULL) {
    posdstb.pos = VG_POS_CENTERED;
    posdstb.x = winw / 2;
    posdstb.y = winh / 2;
    posdst = &posdstb;
  } else {
    posdstb = *posdst;
  }

  switch (posdstb.pos) {
    case VG_POS_UPPER_LEFT:
      cvdata.xadd = posdstb.x;
      cvdata.yadd = posdstb.y;
      break;
    case VG_POS_UPPER_RIGHT:
      cvdata.xadd = posdstb.x - (imgw - 1);
      cvdata.yadd = posdstb.y;
      break;
    case VG_POS_LOWER_LEFT:
      cvdata.xadd = posdstb.x;
      cvdata.yadd = posdstb.y - imgh;
      break;
    case VG_POS_LOWER_RIGHT:
      cvdata.xadd = posdstb.x - (imgw - 1);
      cvdata.yadd = posdstb.y - (imgh - 1);
      break;
    case VG_POS_CENTERED:
      cvdata.xadd = posdstb.x - imgw / 2;
      cvdata.yadd = posdstb.y - imgh / 2;
      break;
    default:
      cvdata.xadd = 0;
      cvdata.yadd = 0;
      break;
  }

#if 0
  /* correct position if canvas is (partially) outside of the window */
  if (cvdata.xadd < 0) {
    posdstb.x -= cvdata.xadd;
    cvdata.xadd = 0;
  } else if (cvdata.xadd + imgw > winw) {
    posdstb.x -= (cvdata.xadd + imgw - winw);
    cvdata.xadd -= (cvdata.xadd + imgw - winw);
  }
  if (cvdata.yadd < 0) {
    posdstb.y -= cvdata.yadd;
    cvdata.yadd = 0;
  } else if (cvdata.yadd + imgh > winh) {
    posdstb.y -= (cvdata.yadd + imgh - winh);
    cvdata.yadd -= (cvdata.yadd + imgh - winh);
  }
#endif

  cvas->cve.midx = cvdata.xadd + imgw / 2;
  cvas->cve.midy = cvdata.yadd + imgh / 2;

  /* warp mouse into canvas if outside */
  if (cvas->cve.mouse.show) {
    int xm, ym;
    if (vg4->input->mouse_position(&xm, &ym)) {
      if (xm < cvdata.xadd || xm >= cvdata.xadd + imgw || ym < cvdata.yadd || ym >= cvdata.yadd + imgh) {
        vg4->input->mouse_warp(cvas->cve.midx, cvas->cve.midy);
      }
    }
  }

  /* keys for navigation */
  keyrefs[CV_KEY_LSHIFT] = vg4->input->key_insert("Left Shift", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_LSHIFT], VG_INPUT_KBDCODE_LSHIFT);
  keyrefs[CV_KEY_RSHIFT] = vg4->input->key_insert("Right Shift", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_RSHIFT], VG_INPUT_KBDCODE_RSHIFT);
  keyrefs[CV_KEY_TAB] = vg4->input->key_insert("Tabulator", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_TAB], VG_INPUT_KBDCODE_TAB);
  keyrefs[CV_KEY_LEFT] = vg4->input->key_insert("Left", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_LEFT], VG_INPUT_KBDCODE_LCURS);
  keyrefs[CV_KEY_UP] = vg4->input->key_insert("Up", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_UP], VG_INPUT_KBDCODE_UCURS);
  keyrefs[CV_KEY_PGUP] = vg4->input->key_insert("Page Up", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_PGUP], VG_INPUT_KBDCODE_PGUP);
  keyrefs[CV_KEY_HOME] = vg4->input->key_insert("Home", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_HOME], VG_INPUT_KBDCODE_HOME);
  keyrefs[CV_KEY_RIGHT] = vg4->input->key_insert("Right", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_RIGHT], VG_INPUT_KBDCODE_RCURS);
  keyrefs[CV_KEY_DOWN] = vg4->input->key_insert("Down", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_DOWN], VG_INPUT_KBDCODE_DCURS);
  keyrefs[CV_KEY_PGDOWN] = vg4->input->key_insert("Page Down", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_PGDOWN], VG_INPUT_KBDCODE_PGDOWN);
  keyrefs[CV_KEY_END] = vg4->input->key_insert("End", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_END], VG_INPUT_KBDCODE_END);
  /* key for sub-selection */
  keyrefs[CV_KEY_SPACE] = vg4->input->key_insert("Next select", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_SPACE], VG_INPUT_KBDCODE_SPACE);
  /* keys for OK and Cancel */
  keyrefs[CV_KEY_RETURN] = vg4->input->key_insert("OK", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_RETURN], VG_INPUT_KBDCODE_RETURN);
  keyrefs[CV_KEY_ESCAPE] = vg4->input->key_insert("Cancel", VG_FALSE, VG_FALSE);
  vg4->input->key_setkbd(keyrefs[CV_KEY_ESCAPE], VG_INPUT_KBDCODE_ESCAPE);

  /* get number of items and index of pre-activated item */
  iactv = -1;
  if (cvas->cve.canvas.max > 0) {
    for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
      if (*cvas->cve.aname != '\0') {
        if (strcmp(cvas->cve.aname, cvas->cve.canvas.e[ielm].name) == 0) { iactv = ielm; }
      }
    }
  }

  /* process additional doing for canvas-types */
  for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
    if (cvinit.type[cvas->cve.canvas.e[ielm].type].pre_exec != NULL) {
      cvinit.type[cvas->cve.canvas.e[ielm].type].pre_exec(cvas, &cvas->cve.canvas.e[ielm]);
    }
  }

  warp_mouse(cvas, iactv, cvdata.xadd, cvdata.yadd);


  /* +++ loop +++ */

  retw = VG_TRUE;
  memset(&cvdata.keys, 0, sizeof(cvdata.keys));

  vg4->input->textbuffer(NULL, 0, NULL, NULL);
  for (;;) {
    /* clear key-states */
    for (kidx = 0; kidx < CV_KEY_MAXENUM; kidx++) {
      if (VGI_CVKEY_RELEASED(cvdata.keys.k[kidx])) {
        cvdata.keys.k[kidx] = CVKEY_IS_NONE;
      } else if (VGI_CVKEY_NEWPRESSED(cvdata.keys.k[kidx])) {
        cvdata.keys.k[kidx] = CVKEY_IS_PRESSED;
      }
    }

    if (!vg4->input->update(VG_FALSE)) { retw = VG_FALSE; break; }

    vg4->input->textbuffer(NULL, 0, NULL, NULL);

    /* set key-states */
    if (!VGI_CVKEY_PRESSED(cvdata.keys.k[CV_KEY_RETURN])) {
      for (kidx = 0; kidx < CV_KEY_MAXENUM; kidx++) {
        if (vg4->input->key_newpressed(keyrefs[kidx])) {
          cvdata.keys.k[kidx] = CVKEY_IS_NEWPRESSED;
        } else if (vg4->input->key_pressed(keyrefs[kidx])) {
          cvdata.keys.k[kidx] = CVKEY_IS_PRESSED;
        } else if (VGI_CVKEY_PRESSED(cvdata.keys.k[kidx])) {
          cvdata.keys.k[kidx] = CVKEY_IS_RELEASED;
        }
      }
    } else if (!vg4->input->key_pressed(keyrefs[CV_KEY_RETURN])) {
      cvdata.keys.k[CV_KEY_RETURN] = CVKEY_IS_RELEASED;
    }

    bact = VG_FALSE;

    /* move to previous/next item? */
    { int imove = 0;
      if (VGI_CVKEY_NEWPRESSED(cvdata.keys.k[CV_KEY_TAB])) {
        if (VGI_CVKEY_PRESSED(cvdata.keys.k[CV_KEY_LSHIFT]) || VGI_CVKEY_PRESSED(cvdata.keys.k[CV_KEY_RSHIFT])) {
          imove = -1;
        } else {
          imove = 1;
        }
      } else if (iactv < 0
                 || !cvinit.type[cvas->cve.canvas.e[iactv].type].uses_cursor
                 || cvas->cve.canvas.e[iactv].isdisabled
                 || !cvas->cve.canvas.e[iactv].isselectable
                 ) {
        if (VGI_CVKEY_NEWPRESSED(cvdata.keys.k[CV_KEY_LEFT]) || VGI_CVKEY_NEWPRESSED(cvdata.keys.k[CV_KEY_UP])) {
          imove = -1;
        } else if (VGI_CVKEY_NEWPRESSED(cvdata.keys.k[CV_KEY_RIGHT]) || VGI_CVKEY_NEWPRESSED(cvdata.keys.k[CV_KEY_DOWN])) {
          imove = 1;
        }
      }

      if (imove != 0) {  /* move */
        ielm = iactv;
        if (imove < 0) {  /* to previous position */
          if (iactv < 0) { iactv = cvas->cve.canvas.max - 1; } else if (iactv > 0) { iactv--; }
          for (; iactv >= 0; iactv--) {
            if (!cvas->cve.canvas.e[iactv].isdisabled && cvas->cve.canvas.e[iactv].isselectable) { break; }
          }
          if (iactv < 0) { iactv = ielm; }
        } else {  /* to next position */
          if (iactv < 0) { iactv = 0; } else if (iactv < cvas->cve.canvas.max - 1) { iactv++; }
          for (; iactv < cvas->cve.canvas.max; iactv++) {
            if (!cvas->cve.canvas.e[iactv].isdisabled && cvas->cve.canvas.e[iactv].isselectable) { break; }
          }
          if (iactv == cvas->cve.canvas.max) { iactv = ielm; }
        }
        warp_mouse(cvas, iactv, cvdata.xadd, cvdata.yadd);
        memset(&cvdata.keys, 0, sizeof(cvdata.keys));
        bact = VG_TRUE;
      }
    }

    /* check mouse position */
    if (cvas->cve.mouse.show) {
      int xm, ym, imrk;
      imrk = iactv;
      iactv = -1;
      if (vg4->input->mouse_position(&xm, &ym)) {
        for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
          if (IS_IN_RECT(&cvas->cve.canvas.e[ielm].rect, xm, ym, cvdata.xadd, cvdata.yadd)) { iactv = ielm; break; }
        }
      }
      if (imrk != iactv) { memset(&cvdata.keys, 0, sizeof(cvdata.keys)); bact = VG_TRUE; }
    }

    if (bact) {
      if (cvas->cve.audc_hover > 0) { vg4->audio->play(cvas->cve.audc_hover, VG_FALSE, VG_FALSE); }
    }

    /* mouse left button (new) pressed? */
    if (cvas->cve.mouse.show && iactv >= 0 && !cvas->cve.canvas.e[iactv].isdisabled && cvas->cve.canvas.e[iactv].isselectable) {
      int mleft;
      if (vg4->input->mouse_newpressed(&mleft, NULL, NULL) && mleft) {
        cvdata.keys.m_left = CVKEY_IS_NEWPRESSED;
      } else if (vg4->input->mouse_pressed(&mleft, NULL, NULL) && mleft) {
        cvdata.keys.m_left = CVKEY_IS_PRESSED;
      } else if (VGI_CVKEY_PRESSED(cvdata.keys.m_left)) {
        cvdata.keys.m_left = CVKEY_IS_RELEASED;
      }
    } else {
      cvdata.keys.m_left = CVKEY_IS_NONE;
    }

    /* Return-key without activated or selectable canvas-element */
    if (iactv < 0 || !cvas->cve.canvas.e[iactv].isselectable) {
      if (VGI_CVKEY_NEWPRESSED(cvdata.keys.k[CV_KEY_RETURN])) { *selname = ""; }
    }


    /* +++ draw +++ */

    vg4->window->clear();
    vg4->window->copy(wclone, NULL, NULL);

    /* main text */
    if (cvas->cve.main.img != NULL) {
      vg4->window->copy(cvas->cve.main.img, &posdstb, NULL);
    }

    /* sprites */
    for (ielm = 0; ielm < cvas->cve.sprites.max; ielm++) {
      if (cvas->cve.sprites.e[ielm].rimg == NULL) { continue; }
      if (*cvas->cve.sprites.e[ielm].textbox != '\0') { continue; }
      if (vg4->sprite->next(cvas->cve.sprites.e[ielm].sprt, &imgp, &iattr)) {
        if (imgp != NULL) {
          vg4->image->clear(cvas->cve.sprites.e[ielm].rimg);
          vg4->image->copy(cvas->cve.sprites.e[ielm].rimg, imgp, NULL, &iattr);
          posb.pos = VG_POS_UPPER_LEFT;
          posb.x = cvdata.xadd + cvas->cve.sprites.e[ielm].rect.x;
          posb.y = cvdata.yadd + cvas->cve.sprites.e[ielm].rect.y;
          vg4->window->copy(cvas->cve.sprites.e[ielm].rimg, &posb, NULL);
        }
      }
    }

    /* images depending on used canvas-type */
    for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
      if (cvinit.type[cvas->cve.canvas.e[ielm].type].draw != NULL) {
        if (ielm == iactv && !cvas->cve.canvas.e[iactv].isdisabled && cvas->cve.canvas.e[iactv].isselectable) {
          cvdata.hasfocus = VG_TRUE;
        } else {
          cvdata.hasfocus = VG_FALSE;
        }
        cvinit.type[cvas->cve.canvas.e[ielm].type].draw(&cvdata, &cvas->cve.canvas.e[ielm], selname);
      }
    }

    /* mouse */
    if (cvas->cve.mouse.show) {
      int xm, ym;
      if (vg4->input->mouse_position(&xm, &ym)) {
        VG_BOOL use_act = VG_FALSE;
        if (iactv >= 0 && !cvas->cve.canvas.e[iactv].isdisabled && cvas->cve.canvas.e[iactv].isselectable) { use_act = VG_TRUE; }
        if (use_act) {
          imgp = vg4_intern_get_img_from_imgcnt(&cvas->cve.mouse.img_act, &iattr);
          if (imgp == NULL) { imgp = vg4_intern_get_img_from_imgcnt(&vg4data.lists.canvas.mouse.img_act, &iattr); }
          if (imgp == NULL) {
            const char *prefix = vg4_get_prefix(NULL);
            char imgfile[VGI_PATHSIZE];
            snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/mouse_act.bmp", prefix, (cvas->cve.ishigh ? VGI_SIZE_HIGH : VGI_SIZE_LOW));
            canvas_load_image(NULL, NULL, imgfile, &vg4data.lists.canvas.mouse.img_act);
            imgp = vg4_intern_get_img_from_imgcnt(&cvas->cve.mouse.img_act, &iattr);
          }
        } else {
          imgp = vg4_intern_get_img_from_imgcnt(&cvas->cve.mouse.img_dfl, &iattr);
          if (imgp == NULL) { imgp = vg4_intern_get_img_from_imgcnt(&vg4data.lists.canvas.mouse.img_dfl, &iattr); }
          if (imgp == NULL) {
            const char *prefix = vg4_get_prefix(NULL);
            char imgfile[VGI_PATHSIZE];
            snprintf(imgfile, sizeof(imgfile), "%s/share/vgagames4/images/%s/mouse_dfl.bmp", prefix, (cvas->cve.ishigh ? VGI_SIZE_HIGH : VGI_SIZE_LOW));
            canvas_load_image(NULL, NULL, imgfile, &vg4data.lists.canvas.mouse.img_dfl);
            imgp = vg4_intern_get_img_from_imgcnt(&cvas->cve.mouse.img_dfl, &iattr);
          }
        }
        if (imgp != NULL) {
          int w1, h1;
          vg4->image->getsize(imgp, NULL, &w1, &h1);
          posb.pos = VG_POS_CENTERED;
          if (cvas->cve.mouse.point_centered) {  /* center of mouse-image */
            posb.x = xm;
            posb.y = ym;
          } else {  /* upper left pixel of mouse-image */
            posb.x = xm + w1 / 2;
            posb.y = ym + h1 / 2;
          }
          /* reduce mouse-opaqueness? */
          if (use_act) {
            if (xm == cvas->cve.mouse.xm && ym == cvas->cve.mouse.ym) {
              if (cvas->cve.mouse.counter > 0) { cvas->cve.mouse.counter--; }
              if (cvas->cve.mouse.counter == 0) { iattr.pixel.opaqueness = 20; }
            } else {
              cvas->cve.mouse.xm = xm;
              cvas->cve.mouse.ym = ym;
              cvas->cve.mouse.counter = 3000 / CV_WAIT_TIME;
            }
          }
          if (iactv >= 0
              && !cvas->cve.canvas.e[iactv].isdisabled
              && cvas->cve.canvas.e[iactv].isselectable
              && cvinit.type[cvas->cve.canvas.e[iactv].type].mouse_opaque
             ) {
            iattr.pixel.opaqueness = 10;
          }
          vg4->window->copy(imgp, &posb, &iattr);
        }
      }
    }

    vg4->window->flush();
    vg4->misc->wait_time(CV_WAIT_TIME);

    if (VGI_CVKEY_NEWPRESSED(cvdata.keys.k[CV_KEY_ESCAPE])) {  /* cancel */
      *selname = NULL;
      break;
    } else if (*selname != NULL && **selname == '\0') {  /* return */
      break;
    } else if (*selname != NULL) {  /* selection was done */
      break;
    }
  }
  vg4->input->textbuffer(NULL, 0, NULL, NULL);

  /* rewind sprites */
  for (ielm = 0; ielm < cvas->cve.sprites.max; ielm++) {
    vg4->sprite->rewind(cvas->cve.sprites.e[ielm].sprt);
  }

  /* remove keys */
  vg4->input->key_remove(keyrefs[CV_KEY_LSHIFT]);
  vg4->input->key_remove(keyrefs[CV_KEY_RSHIFT]);
  vg4->input->key_remove(keyrefs[CV_KEY_TAB]);
  vg4->input->key_remove(keyrefs[CV_KEY_LEFT]);
  vg4->input->key_remove(keyrefs[CV_KEY_UP]);
  vg4->input->key_remove(keyrefs[CV_KEY_PGUP]);
  vg4->input->key_remove(keyrefs[CV_KEY_HOME]);
  vg4->input->key_remove(keyrefs[CV_KEY_RIGHT]);
  vg4->input->key_remove(keyrefs[CV_KEY_DOWN]);
  vg4->input->key_remove(keyrefs[CV_KEY_PGDOWN]);
  vg4->input->key_remove(keyrefs[CV_KEY_END]);
  vg4->input->key_remove(keyrefs[CV_KEY_SPACE]);
  vg4->input->key_remove(keyrefs[CV_KEY_RETURN]);
  vg4->input->key_remove(keyrefs[CV_KEY_ESCAPE]);

  /* remove activation */
  *cvas->cve.aname = '\0';

  /* restore window */
  cvas->cve.w_img = vg4_window_clone_nolist(NULL, NULL);
  vg4->window->clear();
  vg4->window->copy(wclone, NULL, NULL);
  vg4->window->flush();
  vg4->image->destroy(wclone);
  vg4->window->setattr(&wattr_pixel);

  return retw;
} /* Ende canvas_exec */


/* canvas_subcanvas:
 * copy last window-contents from canvas to backbuffer
 * for drawing a new sub-canvas upon the previous
 * @param cvas         canvas
 * @param iattr_pixel  pixel-modifying part of image-copy attributes, or NULL
 * @return  position for sub-canvas (centered to parent-canvas)
 */
static struct VG_Position
canvas_subcanvas(const struct VG_Canvas *cvas, const struct VG_ImagecopyAttrPixel *iattr_pixel)
{
  struct VG_Position posb;

  vg4->window->getsize(&posb.x, &posb.y);
  posb.x /= 2;
  posb.y /= 2;
  posb.pos = VG_POS_CENTERED;

  vg4->window->clear();

  if (cvas == NULL) { return posb; }

  if (cvas->cve.w_img != NULL) {
    struct VG_ImagecopyAttr iattr;
    VG_IMAGECOPY_ATTR_DEFAULT(&iattr);
    if (iattr_pixel != NULL) { iattr.pixel = *iattr_pixel; }
    vg4->window->copy(cvas->cve.w_img, NULL, &iattr);
  }

  if (cvas->cve.midx > 0 || cvas->cve.midy > 0) {
    posb.x = cvas->cve.midx;
    posb.y = cvas->cve.midy;
  }

  return posb;
} /* Ende canvas_subcanvas */


/* canvas_set_mouse:
 * set default mouse-images for all canvasses
 * @param imgfile_dfl     [img:]<default image file> or txt:<default text file> or sprt:<default sprite file>
 * @param imgfile_act     [img:]<activated image file> or txt:<activated text file> or sprt:<activated sprite file>
 * @param point_centered  which pixel of mouse-image points: 1 = center of mouse-image, 0 = upper left pixel of mouse-image
 */
static void
canvas_set_mouse(const char *imgfile_dfl, const char *imgfile_act, int point_centered)
{
  const char *kptr;
  char bprefix[32];

  if (imgfile_dfl == NULL) {
    vg4_intern_free_imgcnt(&vg4data.lists.canvas.mouse.img_dfl);
  } else {
    *bprefix = '\0';
    if ((kptr = strchr(imgfile_dfl, ':')) != NULL) {
      vg4->misc->strscpy(bprefix, sizeof(bprefix), imgfile_dfl, (size_t)(kptr - imgfile_dfl));
      imgfile_dfl = kptr + 1;
    }
    canvas_load_image(NULL, bprefix, imgfile_dfl, &vg4data.lists.canvas.mouse.img_dfl);
  }

  if (imgfile_act == NULL) {
    vg4_intern_free_imgcnt(&vg4data.lists.canvas.mouse.img_act);
  } else {
    *bprefix = '\0';
    if ((kptr = strchr(imgfile_act, ':')) != NULL) {
      vg4->misc->strscpy(bprefix, sizeof(bprefix), imgfile_act, (size_t)(kptr - imgfile_act));
      imgfile_act = kptr + 1;
    }
    canvas_load_image(NULL, bprefix, imgfile_act, &vg4data.lists.canvas.mouse.img_act);
  }

  vg4data.lists.canvas.mouse.point_centered = (point_centered > 0 ? VG_TRUE : VG_FALSE);
} /* Ende canvas_set_mouse */


/* canvas_set_activated:
 * set activated item
 * @param cvas     canvas
 * @param iname    name of item to activate, or NULL = activate nothing
 */
static void
canvas_set_activated(struct VG_Canvas *cvas, const char *iname)
{
  if (cvas == NULL) { return; }

  if (iname == NULL) { iname = ""; }
  vg4->misc->strcpy(cvas->cve.aname, sizeof(cvas->cve.aname), iname);
} /* Ende canvas_set_activated */


/* canvas_disable:
 * disable or enable item
 * @param cvas     canvas
 * @param iname    name of item
 * @param disable  whether to disable
 */
static void
canvas_disable(struct VG_Canvas *cvas, const char *iname, VG_BOOL disable)
{
  int ielm;

  if (cvas == NULL || iname == NULL || *iname == '\0') { return; }

  for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
    if (strcmp(iname, cvas->cve.canvas.e[ielm].name) == 0) {
      cvas->cve.canvas.e[ielm].isdisabled = disable;
      break;
    }
  }
} /* Ende canvas_disable */


/* canvas_is_disabled:
 * return whether item is disabled
 * @param cvas     canvas
 * @param iname    name of item
 * @return  VG_TRUE = disabled, VG_FALSE = enabled
 */
static VG_BOOL
canvas_is_disabled(const struct VG_Canvas *cvas, const char *iname)
{
  int ielm;
  VG_BOOL isdisabled;

  if (cvas == NULL || iname == NULL || *iname == '\0') { return VG_TRUE; }

  isdisabled = VG_TRUE;
  for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
    if (strcmp(iname, cvas->cve.canvas.e[ielm].name) == 0) {
      isdisabled = cvas->cve.canvas.e[ielm].isdisabled;
      break;
    }
  }

  return isdisabled;
} /* Ende canvas_is_disabled */


/* canvas_dump:
 * dump canvas entries
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
static void
canvas_dump(FILE *outfp)
{
  struct VG_Canvas *cvas;
  int ielm, a1;
  const char *namptr;

  if (outfp == NULL) { outfp = stdout; }

  fprintf(outfp, "\nDump of canvas entries\n");
  fprintf(outfp, "======================\n\n");

  namptr = vg4_intern_get_name_from_imgcnt(&vg4data.lists.canvas.mouse.img_dfl);
  fprintf(outfp, "mouse:img-dfl=%s\n", namptr);
  namptr = vg4_intern_get_name_from_imgcnt(&vg4data.lists.canvas.mouse.img_act);
  fprintf(outfp, "mouse:img-act=%s\n", namptr);
  fprintf(outfp, "mouse:point-centered=%d\n", vg4data.lists.canvas.mouse.point_centered);

  if (vg4data.lists.canvas.list == NULL) { return; }

  for (cvas = vg4data.lists.canvas.list->next; cvas != NULL; cvas = cvas->next) {
    fprintf(outfp, "- %s\n", cvas->cve.fname);
    fprintf(outfp, "  file-path=%s\n", cvas->cve.dirname);
    fprintf(outfp, "  name-to-activate=%s\n", (*cvas->cve.aname == '\0' ? "[none]" : cvas->cve.aname));

    s_arrow[0].cvim = &cvas->cve.arrow.low.up;
    s_arrow[1].cvim = &cvas->cve.arrow.low.down;
    s_arrow[2].cvim = &cvas->cve.arrow.low.left;
    s_arrow[3].cvim = &cvas->cve.arrow.low.right;
    fprintf(outfp, "  arrow.low:\n");
    for (a1 = 0; a1 < 4; a1++) {
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_dfl);
      fprintf(outfp, "   - img_dfl=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_act);
      fprintf(outfp, "   - img_act=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_sel);
      fprintf(outfp, "   - img_sel=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_end);
      fprintf(outfp, "   - img_end=%s\n", namptr);
    }

    s_arrow[0].cvim = &cvas->cve.arrow.high.up;
    s_arrow[1].cvim = &cvas->cve.arrow.high.down;
    s_arrow[2].cvim = &cvas->cve.arrow.high.left;
    s_arrow[3].cvim = &cvas->cve.arrow.high.right;
    fprintf(outfp, "  arrow.high:\n");
    for (a1 = 0; a1 < 4; a1++) {
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_dfl);
      fprintf(outfp, "   - img_dfl=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_act);
      fprintf(outfp, "   - img_act=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_sel);
      fprintf(outfp, "   - img_sel=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_end);
      fprintf(outfp, "   - img_end=%s\n", namptr);
    }

    s_arrow[0].cvim = &cvas->cve.arrow.user.up;
    s_arrow[1].cvim = &cvas->cve.arrow.user.down;
    s_arrow[2].cvim = &cvas->cve.arrow.user.left;
    s_arrow[3].cvim = &cvas->cve.arrow.user.right;
    fprintf(outfp, "  arrow.user:\n");
    for (a1 = 0; a1 < 4; a1++) {
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_dfl);
      fprintf(outfp, "   - img_dfl=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_act);
      fprintf(outfp, "   - img_act=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_sel);
      fprintf(outfp, "   - img_sel=%s\n", namptr);
      namptr = vg4->image->getname(s_arrow[a1].cvim->img_end);
      fprintf(outfp, "   - img_end=%s\n", namptr);
    }

    namptr = vg4_intern_get_name_from_imgcnt(&cvas->cve.mouse.img_dfl);
    fprintf(outfp, "  mouse:img-dfl=%s\n", namptr);
    namptr = vg4_intern_get_name_from_imgcnt(&cvas->cve.mouse.img_act);
    fprintf(outfp, "  mouse:img-act=%s\n", namptr);
    fprintf(outfp, "  mouse:point-centered=%d\n", cvas->cve.mouse.point_centered);
    fprintf(outfp, "  mouse:disable=%d\n", !cvas->cve.mouse.show);

    namptr = vg4->image->getname(cvas->cve.main.img);
    fprintf(outfp, "  main.img=%s\n", namptr);
    fprintf(outfp, "  main.htags=%s\n", (cvas->cve.main.htags == NULL ? "[unused]" : "[used]"));

    fprintf(outfp, "  sprites=%d:\n", cvas->cve.sprites.max);
    for (ielm = 0; ielm < cvas->cve.sprites.max; ielm++) {
      fprintf(outfp, "  - [%d]\n", ielm + 1);
      namptr = vg4->sprite->getname(cvas->cve.sprites.e[ielm].sprt);
      fprintf(outfp, "    sprite=%s\n", namptr);
      fprintf(outfp, "    position-itag=%s\n", cvas->cve.sprites.e[ielm].itag);
      fprintf(outfp, "    position-rect={%d+%d,%d+%d}\n",
              cvas->cve.sprites.e[ielm].limrect.x,
              cvas->cve.sprites.e[ielm].limrect.w,
              cvas->cve.sprites.e[ielm].limrect.y,
              cvas->cve.sprites.e[ielm].limrect.h);
      fprintf(outfp, "    absolute rect={%d+%d,%d+%d}\n",
              cvas->cve.sprites.e[ielm].rect.x,
              cvas->cve.sprites.e[ielm].rect.w,
              cvas->cve.sprites.e[ielm].rect.y,
              cvas->cve.sprites.e[ielm].rect.h);
      fprintf(outfp, "    textbox=%s\n", cvas->cve.sprites.e[ielm].textbox);
      namptr = vg4->image->getname(cvas->cve.sprites.e[ielm].rimg);
      fprintf(outfp, "    rimg=%s\n", namptr);
    }

    fprintf(outfp, "  canvas-items=%d:\n", cvas->cve.canvas.max);
    for (ielm = 0; ielm < cvas->cve.canvas.max; ielm++) {
      fprintf(outfp, "  - [%d]\n", ielm + 1);
      fprintf(outfp, "    name=%s\n", cvas->cve.canvas.e[ielm].name);
      fprintf(outfp, "    position-itag=%s\n", cvas->cve.canvas.e[ielm].itag);
      fprintf(outfp, "    position-rect={%d+%d,%d+%d}\n",
              cvas->cve.canvas.e[ielm].limrect.x,
              cvas->cve.canvas.e[ielm].limrect.w,
              cvas->cve.canvas.e[ielm].limrect.y,
              cvas->cve.canvas.e[ielm].limrect.h);
      fprintf(outfp, "    absolute rect={%d+%d,%d+%d}\n",
              cvas->cve.canvas.e[ielm].rect.x,
              cvas->cve.canvas.e[ielm].rect.w,
              cvas->cve.canvas.e[ielm].rect.y,
              cvas->cve.canvas.e[ielm].rect.h);
      namptr = vg4->image->getname(cvas->cve.canvas.e[ielm].rimg);
      fprintf(outfp, "    rimg=%s\n", namptr);
      fprintf(outfp, "    disable=%s\n", (cvas->cve.canvas.e[ielm].isdisabled ? "yes" : "no"));
      if (cvinit.type[cvas->cve.canvas.e[ielm].type].dump != NULL) {
        cvinit.type[cvas->cve.canvas.e[ielm].type].dump(outfp, &cvas->cve.canvas.e[ielm]);
      }
    }
    fprintf(outfp, "\n");
  }
} /* Ende canvas_dump */
