view src/editloop.cc @ 80:2f1ecc1c5f72

Huge cleanup -- move some global variables into a struct.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 26 Sep 2011 17:39:49 +0300
parents e2020fcc4c51
children 002bc70a3982
line wrap: on
line source

/*
 *        editloop.cc
 *        The main loop of the editor.
 *        BW & RQ sometime in 1993 or 1994.
 */


/*
This file is part of Yadex.

Yadex incorporates code from DEU 5.21 that was put in the public domain in
1994 by Raphaël Quinet and Brendon Wyber.

The rest of Yadex is Copyright © 1997-2003 André Majorel and others.

This program 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.

This program 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
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307, USA.
*/


#include "yadex.h"
#include <assert.h>
#include "_edit.h"
#include "checks.h"
#include "dialog.h"
#include "drawmap.h"
#include "edisplay.h"
#include "editgrid.h"
#include "editloop.h"
#include "editobj.h"
#include "editsave.h"
#include "editzoom.h"
#include "entry.h"
#include "entry2.h"
#include "events.h"
#include "gfx.h"
#include "gfx3.h"
#include "gotoobj.h"
#include "help2.h"
#include "l_flags.h"
#include "levels.h"
#include "lists.h"
#include "menubar.h"
#include "menu.h"
#include "modpopup.h"
#include "objects.h"
#include "objid.h"
#include "palview.h"
#include "prefer.h"
#include "rgbbmp.h"
#include "s_slice.h"
#include "selbox.h"
#include "selectn.h"
#include "selpath.h"
#include "spot.h"
#include "t_flags.h"
#include "t_spin.h"
#include "x_centre.h"
#include "x_exchng.h"
#include "x_hover.h"
#include "xref.h"
#include "r_render.h"


static int zoom_fit(edit_t &);
static int menubar_out_y1;        /* FIXME */


/* prototypes of private functions */
static int SortLevels(const void *item1, const void *item2);

/*
 *        SelectLevel
 *        Prompt the user for a level name (EnMn or MAPnm). The
 *        name chosen must be present in the master directory
 *        (iwad or pwads).
 *
 *        If <levelno> is 0, the level name can be picked from all
 *        levels present in the master directory. If <levelno> is
 *        non-zero, the level name can be picked only from those
 *        levels in the master directory for which
 *        levelname2levelno() % 1000 is equal to <levelno>. For
 *        example, if <levelno> is equal to 12, only E1M2 and
 *        MAP12 would be listed. This feature is not used anymore
 *        because "e" now requires an argument and tends to deal
 *        with ambiguous level names (like "12") itself.
 */
const char *SelectLevel(int levelno)
{
    MDirPtr dir;
    static char name[WAD_NAME + 1];        /* AYM it was [7] previously */
    char **levels = 0;
    int n = 0;                        /* number of levels in the dir. that match */

  get_levels_that_match:
    for (dir = cfg.MasterDir; dir; dir = dir->next)
    {
        if (levelname2levelno(dir->dir.name) > 0
            && (levelno == 0
                || levelname2levelno(dir->dir.name) % 1000 == levelno))
        {
            if (n == 0)
                levels = (char **) GetMemory(sizeof(char *));
            else
                levels =
                    (char **) ResizeMemory(levels, (n + 1) * sizeof(char *));
            levels[n] = dir->dir.name;
            n++;
        }
    }
    if (n == 0 && levelno != 0)        /* In case no level matched levelno */
    {
        levelno = 0;                /* List ALL levels instead */
        goto get_levels_that_match;
    }
/* So that InputNameFromList doesn't fail if you
   have both EnMn's and MAPnn's in the master dir. */
    qsort(levels, n, sizeof(char *), SortLevels);
    al_scps(name, levels[0], sizeof name - 1);
    if (n == 1)
        return name;
    InputNameFromList(-1, -1, "Level name :", n, levels, name);
    FreeMemory(levels);
    return name;
}


/*
   compare 2 level names (for sorting)
*/

static int SortLevels(const void *item1, const void *item2)
{
/* FIXME should probably use y_stricmp() instead */
    return strcmp(*((const char *const *) item1),
                  *((const char *const *) item2));
}


// A table of the modes in the editor.
typedef struct
{
    i8 obj_type;                // Corresponding object type
    i8 item_no;                        // # of item to tick in the "View" menu
    i8 menu_no;                        // # of flavour of the "Misc op." menu
} editmode_t;

const int NB_MODES = 4;

static const editmode_t modes[NB_MODES] = {
    {OBJ_THINGS, 0, MBM_MISC_T},
    {OBJ_LINEDEFS, 1, MBM_MISC_L},
    {OBJ_VERTICES, 2, MBM_MISC_V},
    {OBJ_SECTORS, 3, MBM_MISC_S},
};


/*
 *        obj_type_to_mode_no
 *        Return the # of the mode that has this <obj_type>
 */
static int obj_type_to_mode_no(int obj_type)
{
    int n;
    for (n = 0; n < NB_MODES; n++)
        if (modes[n].obj_type == obj_type)
            break;
    if (n == NB_MODES)
        fatal_error("no mode for obj_type %d", obj_type);
    return n;
}


// Used by the View menu
bool mode_th(micbarg_t p)
{
    return ((edit_t *) p)->obj_type == OBJ_THINGS;
}

bool mode_l(micbarg_t p)
{
    return ((edit_t *) p)->obj_type == OBJ_LINEDEFS;
}

bool mode_v(micbarg_t p)
{
    return ((edit_t *) p)->obj_type == OBJ_VERTICES;
}

bool mode_s(micbarg_t p)
{
    return ((edit_t *) p)->obj_type == OBJ_SECTORS;
}


/*
  the editor main loop
*/

void EditorLoop(const char *levelname)        /* SWAP! */
{
    edit_t e;
/* FIXME : all these variables should be moved to edit_t : */
    int RedrawMap;
    bool DragObject = false, DragMapMove = false;
    int oldbuttons, DragMapMoveX, DragMapMoveY;

    bool StretchSelBox = false;        // FIXME apparently not used anymore...

    Objid object;                // The object under the pointer
    const Objid CANVAS(OBJ_NONE, OBJ_NO_CANVAS);

    memset(&e, 0, sizeof e);        /* Catch-all */
    e.move_speed = 20;
    e.extra_zoom = 0;
// If you change this, don't forget to change
// the initialisation of the menu bar below.
    e.obj_type = OBJ_THINGS;
    e.global = false;
    e.tool = TOOL_NORMAL;
    e.grid_step = 128;
    e.grid_step_min = cfg.GridMin;
    e.grid_step_max = cfg.GridMax;
    e.grid_step_locked = 0;
    e.grid_shown = 1;
    e.grid_snap = 1;
    e.infobar_shown = cfg.InfoShown;
    e.objinfo_shown = true;
    e.show_object_numbers = false;
    e.show_things_squares = false;
    e.show_things_sprites = true;
    e.rulers_shown = 0;
    e.clicked.nil();
//e.click_obj_no        = OBJ_NO_NONE;
//e.click_obj_type      = -1;
    e.click_ctrl = 0;
    e.highlighted.nil();
//e.highlight_obj_no    = OBJ_NO_NONE;
//e.highlight_obj_type  = -1;
    e.Selected = 0;
    e.selbox = new selbox_c;
    e.edisplay = new edisplay_c(&e);
    e.menubar = new menubar_c;
    e.spot = new spot_c;
    e.modpopup = new modpopup_c;
    e.modal = '\0';

    MadeChanges = 0;
    MadeMapChanges = 0;

// Sane defaults
    cfg.Scale = 1.0;
    cfg.OrigX = 0;
    cfg.OrigY = 0;

    edit_zoom_init();

    if (cfg.zoom_default == 0)
    {
        zoom_fit(e);
    }
    else
    {
        int r = edit_set_zoom(&e, cfg.zoom_default / 100.0);
        if (r == 0)
            CenterMapAroundCoords((MapMinX + MapMaxX) / 2,
                                  (MapMinY + MapMaxY) / 2);
    }
    oldbuttons = 0;

/* Create the menu bar */
    {
        e.menubar->compute_menubar_coords(0, 0, cfg.ScrMaxX, cfg.ScrMaxY);

        e.mb_menu[MBM_FILE] = new Menu(NULL,
                                       "~Save", YK_F2, 0,
                                       "Save ~as...", YK_F3, 0,
// "~Print",      YK_,   MIF_SACTIVE, false, 0,
                                       "~Quit", 'q', 0, NULL);

        e.mb_menu[MBM_EDIT] = new Menu(NULL,
                                       "~Copy object(s)", 'o', 0,
                                       "~Add object", YK_INS, 0,
                                       "~Delete object(s)", YK_DEL, 0,
                                       "~Exchange object numbers", 24, 0,
                                       "~Preferences...", YK_F5, 0,
                                       "~Snap to grid", 'y', MIF_VTICK,
                                       &e.grid_snap, 0, "~Lock grid step",
                                       'z', MIF_VTICK, &e.grid_step_locked, 0,
                                       NULL);

// If you change the order of modes here, don't forget
// to modify the <modes> array.
        e.mb_menu[MBM_VIEW] = new Menu(NULL,
                                       "~Things", 't', MIF_FTICK, mode_th,
                                       (micbarg_t) & e, 0,
                                       "~Linedefs & sidedefs", 'l', MIF_FTICK,
                                       mode_l, (micbarg_t) & e, 0,
                                       "~Vertices", 'v', MIF_FTICK, mode_v,
                                       (micbarg_t) & e, 0, "~Sectors", 's',
                                       MIF_FTICK, mode_s, (micbarg_t) & e, 0,
                                       "Global", '\7', MIF_VTICK, &e.global,
                                       0, "~Next mode", YK_TAB, 0,
                                       "~Prev mode", YK_BACKTAB, 0,
                                       MI_SEPARATION, "Zoom ~in", '+', 0,
                                       "Zoom ~out", '-', 0, "Extra ~zoom",
                                       ' ', MIF_VTICK, &e.extra_zoom, 0,
                                       "~Whole level", '`', 0, MI_SEPARATION,
                                       "Show object numbers", '&', MIF_VTICK,
                                       &e.show_object_numbers, 0,
                                       "Show sprites", '%', MIF_VTICK,
                                       &e.show_things_sprites, 0,
                                       "Show ~grid", 'h', MIF_VTICK,
                                       &e.grid_shown, 0, "Info bar",
                                       YK_ALT + 'i', MIF_VTICK,
                                       &e.infobar_shown, 0,
                                       "Object info boxes", 'i', MIF_VTICK,
                                       &e.objinfo_shown, 0,
// "3D preview",           '3',        MIF_SACTIVE, false,                   0,
                                       NULL);

        e.mb_menu[MBM_SEARCH] = new Menu(NULL,
// "~Find/change",       YK_F4, MIF_SACTIVE, false, 0,
// "~Repeat last find",  -1,    MIF_SACTIVE, false, 0,
                                         "~Next object", 'n', 0,
                                         "~Prev object", 'p', 0,
                                         "~Jump to object...", 'j', 0,
                                         "~Find by type", 'f', 0, NULL);

        e.mb_menu[MBM_MISC_L] = new Menu("Misc. operations",
                                         "Find first free ~tag number", YK_,
                                         0, "~Rotate and scale linedefs...",
                                         YK_, 0,
                                         "Split linedefs (add new ~vertex)",
                                         YK_, 0, "~Split linedefs and sector",
                                         YK_, 0,
                                         "~Delete linedefs and join sectors",
                                         YK_, 0, "~Flip linedefs", YK_, 0,
                                         "S~wap sidedefs", YK_, 0,
                                         "Align textures (~Y offset)", YK_, 0,
                                         "Align textures (~X offset)...", YK_,
                                         0,
                                         "Remove ~2nd sidedef (make single-sided)",
                                         YK_, 0,
                                         "Make rectangular ~nook (32x16)",
                                         YK_, 0,
                                         "Make rectangular ~boss (32x16)",
                                         YK_, 0,
                                         "Set ~length (move 1st vertex)...",
                                         YK_, 0,
                                         "Set length (move 2nd vertex)...",
                                         YK_, 0, "~Unlink 1st sidedef", YK_,
                                         0, "Unlink 2nd sidedef", YK_, 0,
                                         "~Mirror horizontally", YK_, 0,
                                         "Mirror v~ertically", YK_, 0,
                                         "~Cut a slice out of a sector", YK_,
                                         0, NULL);

        e.mb_menu[MBM_MISC_S] = new Menu("Misc. operations",
                                         "Find first free ~tag number", YK_,
                                         0, "~Rotate and scale sectors...",
                                         YK_, 0, "Make ~door from sector",
                                         YK_, 0, "Make ~lift from sector",
                                         YK_, 0,
                                         "Distribute sector ~floor heights",
                                         YK_, 0,
                                         "Distribute sector ~ceiling heights",
                                         YK_, 0, "R~aise or lower sectors...",
                                         YK_, 0,
                                         "~Brighten or darken sectors...",
                                         YK_, 0, "~Unlink room", YK_, 0,
                                         "~Mirror horizontally", YK_, 0,
                                         "Mirror ~vertically", YK_, 0,
                                         "~Swap flats", YK_, 0, NULL);

        e.mb_menu[MBM_MISC_T] = new Menu("Misc. operations",
                                         "Find first free ~tag number", YK_,
                                         0, "~Rotate and scale things...",
                                         YK_, 0, "~Spin things 45° clockwise",
                                         'x', 0,
                                         "Spin things 45° ~counter-clockwise",
                                         'w', 0, "~Mirror horizontally", YK_,
                                         0, "Mirror ~vertically", YK_, 0,
                                         NULL);

        e.mb_menu[MBM_MISC_V] = new Menu("Misc. operations",
                                         "Find first free ~tag number", YK_,
                                         0, "~Rotate and scale vertices...",
                                         YK_, 0,
                                         "~Delete vertex and join linedefs",
                                         YK_, 0,
                                         "~Merge several vertices into one",
                                         YK_, 0,
                                         "Add a linedef and ~split sector",
                                         YK_, 0, "Mirror ~horizontally", YK_,
                                         0, "Mirror ~vertically", YK_, 0,
                                         NULL);

        e.mb_menu[MBM_OBJECTS] = new Menu("Insert a pre-defined object",
                                          "~Rectangle...", YK_, 0,
                                          "~Polygon (N sides)...", YK_, 0,
                                          NULL);

        e.mb_menu[MBM_CHECK] = new Menu("Check level consistency",
                                        "~Number of objects", YK_, 0,
                                        "Check if all ~sectors are closed",
                                        YK_, 0, "Check all ~cross-references",
                                        YK_, 0, "Check for ~missing textures",
                                        YK_, 0, "Check ~texture names", YK_,
                                        0, NULL);

        e.mb_menu[MBM_HELP] = new Menu(NULL,
                                       "~Keyboard & mouse...", YK_F1, 0,
                                       "~About Yadex...", YK_ALT + 'a', 0,
                                       NULL);

        e.mb_ino[MBI_FILE] =
            e.menubar->add_item("File", 0, 0, e.mb_menu[MBM_FILE]);
        e.mb_ino[MBI_EDIT] =
            e.menubar->add_item("Edit", 0, 0, e.mb_menu[MBM_EDIT]);
        e.mb_ino[MBI_VIEW] =
            e.menubar->add_item("View", 0, 0, e.mb_menu[MBM_VIEW]);
        e.mb_ino[MBI_SEARCH] =
            e.menubar->add_item("Search", 0, 0, e.mb_menu[MBM_SEARCH]);
        e.mb_ino[MBI_MISC] =
            e.menubar->add_item("Misc", 0, 0, e.mb_menu[MBM_MISC_T]);
        e.mb_ino[MBI_OBJECTS] =
            e.menubar->add_item("Objects", 0, 0, e.mb_menu[MBM_OBJECTS]);
        e.mb_ino[MBI_CHECK] =
            e.menubar->add_item("Check", 0, 0, e.mb_menu[MBM_CHECK]);
        e.mb_ino[MBI_HELP] =
            e.menubar->add_item("Help", 0, 1, e.mb_menu[MBM_HELP]);

        menubar_out_y1 = 2 * BOX_BORDER + 2 * NARROW_VSPACING + FONTH - 1;        // FIXME
    }

// FIXME this should come from the .ygd
// instead of being hard-coded.
    Menu *menu_linedef_flags = new Menu(NULL,
                                        "~Impassable", YK_, 0,
                                        "~Monsters cannot cross", YK_, 0,
                                        "~Double-sided", YK_, 0,
                                        "~Upper texture unpegged", YK_, 0,
                                        "~Lower texture unpegged", YK_, 0,
                                        "~Secret (shown as normal)", YK_, 0,
                                        "~Blocks sound", YK_, 0,
                                        "~Never shown on the map", YK_, 0,
                                        "~Always shown on the map", YK_, 0,
                                        "~Pass through [Boom]", YK_, 0,        // Boom extension
                                        "b1~0 0400h", YK_, 0,        // Undefined
                                        "b1~1 0800h", YK_, 0,        // Undefined
                                        "~Translucent [Strife]", YK_, 0,        // Strife
                                        "b1~3 2000h", YK_, 0,        // Undefined
                                        "b1~4 4000h", YK_, 0,        // Undefined
                                        "b1~5 8000h", YK_, 0,        // Undefined
                                        NULL);

    Menu *menu_thing_flags = new Menu(NULL,
                                      "~Easy", YK_, 0,
                                      "Medi~um", YK_, 0,
                                      "~Hard", YK_, 0,
                                      "~Deaf", YK_, 0,
                                      "~Multiplayer", YK_, 0,
                                      "~Not in DM [Boom]", YK_, 0,        // Boom extension
                                      "Not in ~coop [Boom]", YK_, 0,        // Boom extension
                                      "F~riendly [MBF]", YK_, 0,        // MBF extension
                                      "b~8  0100h", YK_, 0,        // Undefined
                                      "b~9  0200h", YK_, 0,        // Undefined
                                      "b1~0 0400h", YK_, 0,        // Undefined
                                      "b1~1 0800h", YK_, 0,        // Undefined
                                      "b1~2 1000h", YK_, 0,        // Undefined
                                      "b1~3 2000h", YK_, 0,        // Undefined
                                      "b1~4 4000h", YK_, 0,        // Undefined
                                      "b1~5 8000h", YK_, 0,        // Undefined
                                      NULL);

/* AYM 1998-06-22
   This is the big mean loop. I organized it in three main steps :

   1. Update the display according to the current state of affairs.
   2. Wait for the next event (key press, mouse click/motion, etc.).
   3. Process this event and change states as appropriate.

   This piece of code remembers a lot of state in various variables and
   flags. Hope you can work your way through it. If you don't, don't
   hesitate to ask me. You never know, I might help you to be even more
   confused. ;-) */

    for (RedrawMap = 1;; RedrawMap = 0)
    {
        int motion = 0;                // Initialized to silence GCC warning

        /*
         *  Step 1 -- Do all the displaying work
         */

#ifdef Y_BATCH
        // Hold refresh until all events are processed
        if (!has_event() && !has_input_event())
        {
#endif
            if (e.highlighted())        // FIXME
                e.edisplay->highlight_object(e.highlighted);        // Should
            else                // be in
                e.edisplay->forget_highlight();        // edisplay_c !

            if (is.in_window)
                e.spot->set(edit_mapx_snapped(&e, e.pointer_x),
                            edit_mapy_snapped(&e, e.pointer_y));
            else
                e.spot->unset();

            e.edisplay->refresh();
            /* The display is now up to date */
#ifdef Y_BATCH
        }
#endif


        /*
         *  Step 2 -- Get the next event
         */

        if (has_key_press_event())
            is.key = get_event();
        else
            get_input_status();
        e.pointer_in_window = is.in_window;
        if (is.in_window)
        {
            /* AYM 1998-07-04
               If the map coordinates of the pointer have changed,
               generate a pointer motion event. I don't like to do
               that but it makes things much simpler elsewhere. */
            if (MAPX(is.x) != e.pointer_x || MAPY(is.y) != e.pointer_y)
                motion = 1;
            else
                motion = 0;

            e.pointer_x = MAPX(is.x);
            e.pointer_y = MAPY(is.y);
            obj_type_t t = e.global ? OBJ_ANY : e.obj_type;
            GetCurObject(object, t, e.pointer_x, e.pointer_y);
        }

        /*
         *  Step 3 -- Process the event
         *  This section is (should be) a long list of elif's
         */

        /*
         *   Step 3.1A -- If a pull-down menu is "on",
         *   try to make it process the event.
         */

        if (e.menubar->pulled_down() >= 0)
        {
            int menu_no = e.menubar->pulled_down();
            Menu *menu = e.menubar->get_menu(menu_no);
            int r = menu->process_event(&is);

            if (r == MEN_CANCEL)
            {
                e.menubar->pull_down(-1);
                e.menubar->highlight(-1);
                goto done;
            }

            // The event was understood and processed normally
            // by the menu event handler so we're done.
            else if (r == MEN_OTHER)
                goto done;

            // The event was not understood by the menu event
            // handler so let's see what the normal event handler
            // can do with it.
            else if (r == MEN_INVALID)
                ;

            else
            {
                e.menubar->pull_down(-1);
                e.menubar->highlight(-1);
                if (menu_no == e.mb_ino[MBI_MISC])
                {
                    if (e.Selected)
                        MiscOperations(e.obj_type, &e.Selected, r + 1);
                    else
                    {
                        if (e.highlighted())
                            SelectObject(&e.Selected, e.highlighted.num);
                        MiscOperations(e.obj_type, &e.Selected, r + 1);
                        if (e.highlighted())
                            UnSelectObject(&e.Selected, e.highlighted.num);
                    }
                    e.highlighted.nil();
                    DragObject = false;
                    StretchSelBox = false;
                    RedrawMap = 1;
                }
                else if (menu_no == e.mb_ino[MBI_OBJECTS])
                {
                    /* code duplicated from 'F9' - I hate to do that */
                    int savednum = NumLineDefs;
                    InsertStandardObject(e.pointer_x, e.pointer_y, r + 1);
                    if (NumLineDefs > savednum)
                    {
                        ForgetSelection(&e.Selected);
                        e.obj_type = OBJ_LINEDEFS;
                        for (int i = savednum; i < NumLineDefs; i++)
                            SelectObject(&e.Selected, i);
                        e.highlighted.nil();
                        DragObject = false;
                        StretchSelBox = false;
                    }
                    RedrawMap = 1;
                }
                else if (menu_no == e.mb_ino[MBI_CHECK])
                {
                    if (r == 0)
                        Statistics();
                    else if (r == 1)
                        CheckSectors();
                    else if (r == 2)
                        CheckCrossReferences();
                    else if (r == 3)
                        CheckTextures();
                    else if (r == 4)
                        CheckTextureNames();
                }
                else
                    send_event(menu->last_shortcut_key());
                goto done;
            }
        }

        /*
         *   Step 3.1B -- If the "Misc operations" popup is on,
         *   try to make it process the event.
         */

        else if (e.modpopup->get() == e.mb_menu[MBM_MISC_L]
                 || e.modpopup->get() == e.mb_menu[MBM_MISC_S]
                 || e.modpopup->get() == e.mb_menu[MBM_MISC_T]
                 || e.modpopup->get() == e.mb_menu[MBM_MISC_V])
        {
            int r = (e.modpopup->get())->process_event(&is);

            // [Esc] or click outside the popup menu. Close it.
            if (r == MEN_CANCEL || (r == MEN_INVALID && is.key == YE_BUTL_PRESS))
            {
                e.modpopup->unset();
                goto done2;
            }

            // The event was understood and processed normally
            // by the menu event handler so we're done.
            else if (r == MEN_OTHER)
                goto done2;

            // The event was not understood by the menu event
            // handler so let's see what the normal event handler
            // can do with it.
            else if (r == MEN_INVALID)
                ;

            else
            {
                e.modpopup->unset();
                if (e.Selected)
                    MiscOperations(e.obj_type, &e.Selected, r + 1);
                else
                {
                    if (e.highlighted())
                        SelectObject(&e.Selected, e.highlighted.num);
                    MiscOperations(e.obj_type, &e.Selected, r + 1);
                    if (e.highlighted())
                        UnSelectObject(&e.Selected, e.highlighted.num);
                }
                e.highlighted.nil();
                DragObject = false;
                StretchSelBox = false;
                goto done2;
            }
        }

        /*
         *   Step 3.1C -- If the "Insert standard object" popup is on,
         *   try to make it process the event.
         */

        else if (e.modpopup->get() == e.mb_menu[MBM_OBJECTS])
        {
            int r = (e.modpopup->get())->process_event(&is);

            // [Esc] or click outside the popup menu. Close it.
            if (r == MEN_CANCEL || (r == MEN_INVALID && is.key == YE_BUTL_PRESS))
            {
                e.modpopup->unset();
                goto done2;
            }

            // The event was understood and processed normally
            // by the menu event handler so we're done.
            else if (r == MEN_OTHER)
                goto done2;

            // The event was not understood by the menu event
            // handler so let's see what the normal event handler
            // can do with it.
            else if (r == MEN_INVALID)
                ;

            else
            {
                e.modpopup->unset();
                int savednum = NumLineDefs;
                InsertStandardObject(e.pointer_x, e.pointer_y, r + 1);
                if (NumLineDefs > savednum)
                {
                    ForgetSelection(&e.Selected);
                    e.obj_type = OBJ_LINEDEFS;
                    for (int i = savednum; i < NumLineDefs; i++)
                        SelectObject(&e.Selected, i);
                    e.highlighted.nil();
                    DragObject = false;
                    StretchSelBox = false;
                }
                goto done2;
            }
        }

        /*
         *   Step 3.1D -- if the "Set/toggle/clear" linedef flag popup
         *   is on, try to make it process the event.
         */
        else if (e.modpopup->get() == menu_linedef_flags)
        {
            int r = (e.modpopup->get())->process_event(&is);

            // [Esc] or click outside the popup menu. Close it.
            if (r == MEN_CANCEL || (r == MEN_INVALID && is.key == YE_BUTL_PRESS))
            {
                e.modpopup->unset();
                goto done2;
            }

            // The event was understood and processed normally
            // by the menu event handler so we're done.
            else if (r == MEN_OTHER)
                goto done2;

            // The event was not understood by the menu event
            // handler so let's see what the normal event handler
            // can do with it.
            else if (r == MEN_INVALID)
                ;

            else
            {
                int op = -1;
                e.modpopup->unset();
                if (e.modal == 's')
                    op = YO_SET;
                else if (e.modal == 't')
                    op = YO_TOGGLE;
                else if (e.modal == 'c')
                    op = YO_CLEAR;
                else
                    fatal_error("modal=%02X", e.modal);
                if (!e.Selected)
                {
                    SelectObject(&e.Selected, e.highlighted.num);
                    frob_linedefs_flags(e.Selected, op, r);
                    UnSelectObject(&e.Selected, e.highlighted.num);
                }
                else
                {
                    frob_linedefs_flags(e.Selected, op, r);
                }
                goto done2;
            }
        }

        /*
         *   Step 3.1E -- if the "Set/toggle/clear" thing flag popup
         *   is on, try to make it process the event.
         */
        else if (e.modpopup->get() == menu_thing_flags)
        {
            int r = (e.modpopup->get())->process_event(&is);

            // [Esc] or click outside the popup menu. Close it.
            if (r == MEN_CANCEL || (r == MEN_INVALID && is.key == YE_BUTL_PRESS))
            {
                e.modpopup->unset();
                goto done2;
            }

            // The event was understood and processed normally
            // by the menu event handler so we're done.
            else if (r == MEN_OTHER)
                goto done2;

            // The event was not understood by the menu event
            // handler so let's see what the normal event handler
            // can do with it.
            else if (r == MEN_INVALID)
                ;

            else
            {
                int op = -1;
                e.modpopup->unset();
                if (e.modal == 's')
                    op = YO_SET;
                else if (e.modal == 't')
                    op = YO_TOGGLE;
                else if (e.modal == 'c')
                    op = YO_CLEAR;
                else
                    fatal_error("modal=%02X", e.modal);
                if (!e.Selected)
                {
                    SelectObject(&e.Selected, e.highlighted.num);
                    frob_things_flags(e.Selected, op, r);
                    UnSelectObject(&e.Selected, e.highlighted.num);
                }
                else
                {
                    frob_things_flags(e.Selected, op, r);
                }
                goto done2;
            }
        }

        /*
         *   Step 3.2 -- "Normal" event handling
         */

        /*
         *   Step 3.2.1 -- Non keyboard events
         */

        if (is.key == YE_EXPOSE)
        {
            RedrawMap = 1;
            goto done2;
        }

        else if (is.key == YE_RESIZE)
        {
            SetWindowSize(is.width, is.height);
            e.menubar->compute_menubar_coords(0, 0, cfg.ScrMaxX, cfg.ScrMaxY);
            RedrawMap = 1;
            goto done2;
        }

        // To prevent normal handling when a popup menu is on.
        if (e.modpopup->get() != 0)
        {
            goto done2;
        }

        /*
         *   Step 3.2.2 -- Mouse events
         */
        if (DragMapMove && is.key == YE_MOTION)
        {
            int deltaX = DragMapMoveX - is.x, deltaY = DragMapMoveY - is.y;

            if (deltaX != 0 || deltaY != 0)
            {
                cfg.OrigX += (deltaX / cfg.Scale);
                cfg.OrigY -= (deltaY / cfg.Scale);
                RedrawMap = 1;
                DragMapMoveX = is.x;
                DragMapMoveY = is.y;
                goto done2;
            }
        }

        else if (is.key == YE_BUTM_PRESS)
        {
            DragMapMove = true;
            DragMapMoveX = is.x;
            DragMapMoveY = is.y;
            goto done;
        }

        else if (is.key == YE_BUTM_RELEASE)
        {
            DragMapMove = false;
            goto done;
        }

        // Clicking on an item of the menu bar
        // pulls down the corresponding menu.
        else if (is.key == YE_BUTL_PRESS
                 && e.menubar->is_on_menubar_item(is.x, is.y) >= 0)
        {
            int itemno;

            e.clicked.nil();
            itemno = e.menubar->is_on_menubar_item(is.x, is.y);
            if (itemno >= 0)
                e.menubar->pull_down(itemno);
            else
                Beep();
            goto done;
        }

        /* Clicking on an empty space starts a new selection box.
           Unless [Ctrl] is pressed, it also clears the current selection. */
        else if (is.key == YE_BUTL_PRESS
                 && e.tool == TOOL_NORMAL && object.is_nil())
        {
            e.menubar->highlight(-1);        // Close any open menu
            e.clicked = CANVAS;
            e.click_ctrl = is.ctrl;
            if (!is.ctrl)
            {
                ForgetSelection(&e.Selected);
                RedrawMap = 1;
            }
            e.selbox->set_1st_corner(e.pointer_x, e.pointer_y);
            e.selbox->set_2nd_corner(e.pointer_x, e.pointer_y);
        }

        /* Clicking on an unselected object unselects
           everything but that object. Additionally,
           we write the number of the object in case
           the user is about to drag it. */
        else if (is.key == YE_BUTL_PRESS && !is.ctrl
                 && e.tool == TOOL_NORMAL
                 && !IsSelected(e.Selected, object.num))
        {
            e.menubar->highlight(-1);        // Close any open menu
            e.clicked = object;
            e.click_ctrl = 0;
            e.click_time = is.time;
            ForgetSelection(&e.Selected);
            SelectObject(&e.Selected, object.num);
            /* I don't like having to do that */
            if (object.type == OBJ_THINGS && object())
                MoveObjectsToCoords(object.type, 0,
                                    Things[object.num].xpos,
                                    Things[object.num].ypos, 0);
            else if (object.type == OBJ_VERTICES && object())
                MoveObjectsToCoords(object.type, 0,
                                    Vertices[object.num].x,
                                    Vertices[object.num].y, 0);
            else
                MoveObjectsToCoords(object.type, 0,
                                    e.pointer_x, e.pointer_y,
                                    e.grid_snap ? e.grid_step : 0);
            RedrawMap = 1;
        }

        /* Second click of a double click on an object */
        else if (is.key == YE_BUTL_PRESS && !is.ctrl
                 && e.tool == TOOL_NORMAL
                 && IsSelected(e.Selected, object.num)
                 && object == e.clicked
                 && is.time - e.click_time <=
                 (unsigned long) cfg.double_click_timeout)
        {
            // Very important! If you don't do that, the release of the
            // click that closed the properties menu will drag the object.
            e.clicked.nil();
            send_event(YK_RETURN);
            goto done;
        }

        /* Clicking on a selected object does nothing ;
           the user might want to drag the selection. */
        else if (is.key == YE_BUTL_PRESS && !is.ctrl
                 && e.tool == TOOL_NORMAL
                 && IsSelected(e.Selected, object.num))
        {
            e.menubar->highlight(-1);        // Close any open menu
            e.clicked = object;
            e.click_ctrl = 0;
            e.click_time = is.time;
            /* I don't like having to do that */
            if (object.type == OBJ_THINGS && object())
                MoveObjectsToCoords(object.type, 0,
                                    Things[object.num].xpos,
                                    Things[object.num].ypos, 0);
            else if (object.type == OBJ_VERTICES && object())
                MoveObjectsToCoords(object.type, 0,
                                    Vertices[object.num].x,
                                    Vertices[object.num].y, 0);
            else
                MoveObjectsToCoords(object.type, 0,
                                    e.pointer_x, e.pointer_y,
                                    e.grid_snap ? e.grid_step : 0);
        }

        /* Clicking on selected object with [Ctrl] pressed unselects it.
           Clicking on unselected object with [Ctrl] pressed selects it. */
        else if (((is.key == YE_BUTL_PRESS && is.ctrl)
                  || is.key == YE_BUTR_PRESS) && e.tool == TOOL_NORMAL
                 && object())
        {
            e.menubar->highlight(-1);        // Close any open menu
            e.clicked = object;
            e.click_ctrl = 1;
            if (IsSelected(e.Selected, object.num))
                UnSelectObject(&e.Selected, object.num);
            else
                SelectObject(&e.Selected, object.num);
            RedrawMap = 1;
        }

        /* TOOL_SNAP_VERTEX */
        else if (is.key == YE_BUTL_PRESS
                 && e.tool == TOOL_SNAP_VERTEX
                 && e.obj_type == OBJ_VERTICES && object()
                 // Can't delete vertex that is referenced by the selection
                 && !IsSelected(e.Selected, object.num))
        {
            printf("SNAP %d\n", (int) object.num);
            SelPtr list = 0;
            SelectObject(&list, object.num);
            DeleteVerticesJoinLineDefs(list);
            ForgetSelection(&list);
            RedrawMap = 1;
        }

        /* Clicking anywhere else closes the pull-down menus. */
        else if (is.key == YE_BUTL_PRESS)
            e.menubar->highlight(-1);        // Close any open menu

        /* Releasing the button while there was a selection box
           causes all the objects within the box to be selected. */
        // FIXME : should call this automatically when switching tool
        else if (is.key == YE_BUTL_RELEASE
                 && e.tool == TOOL_NORMAL && e.clicked == CANVAS)
        {
            int x1, y1, x2, y2;
            e.selbox->get_corners(&x1, &y1, &x2, &y2);
            SelectObjectsInBox(&e.Selected, e.obj_type, x1, y1, x2, y2);
            e.selbox->unset_corners();
            RedrawMap = 1;
        }

        /* Releasing the button while dragging : drop the selection. */
        // FIXME : should call this automatically when switching tool
        else if (is.key == YE_BUTL_RELEASE
                 && e.tool == TOOL_NORMAL && e.clicked())
        {
            if (AutoMergeVertices(&e.Selected, e.obj_type, 'm'))
                RedrawMap = 1;
        }

        // Moving the pointer with the left button pressed
        // after clicking on an item of the menu bar : pull
        // down menus as the pointer passes over them.
        else if (is.key == YE_MOTION
                 && e.tool == TOOL_NORMAL
                 && is.butl && !e.clicked() && !(e.clicked == CANVAS))
        {
            int itemno = e.menubar->is_on_menubar_item(is.x, is.y);
            if (itemno >= 0)
                e.menubar->pull_down(itemno);
            goto done;
        }

        /* Moving the pointer with the left button pressed
           and a selection box exists : move the second
           corner of the selection box. */
        else if ((is.key == YE_MOTION || motion)
                 && e.tool == TOOL_NORMAL && is.butl && e.clicked == CANVAS)
        {
            e.selbox->set_2nd_corner(e.pointer_x, e.pointer_y);
        }

        /* Moving the pointer with the left button pressed
           but no selection box exists and [Ctrl] was not
           pressed when the button was pressed :
           drag the selection. */
        else if (motion
                 && e.tool == TOOL_NORMAL
                 && is.butl && e.clicked() && !e.click_ctrl)
        {
            if (!e.Selected)
            {
                SelectObject(&e.Selected, e.clicked.num);
                if (MoveObjectsToCoords(e.clicked.type, e.Selected,
                                        e.pointer_x, e.pointer_y,
                                        e.grid_snap ? e.grid_step : 0))
                    RedrawMap = 1;
                ForgetSelection(&e.Selected);
            }
            else if (MoveObjectsToCoords(e.clicked.type, e.Selected,
                                         e.pointer_x, e.pointer_y,
                                         e.grid_snap ? e.grid_step : 0))
                RedrawMap = 1;
        }

        /* AYM : added is.in_window */
        if (is.in_window && !is.butl && !is.shift)
        {
            /* Check if there is something near the pointer */
            e.highlighted = object;
        }

        /*
         *   Step 3.2.3 -- Keyboard events
         */

        if (event_is_key(is.key)
            || is.key == YE_WHEEL_UP || is.key == YE_WHEEL_DOWN)
        {
            if (is.key == YK_LEFT && e.menubar->highlighted() >= 0)
            {
                int new_item = e.menubar->highlighted() - 1;
                if (new_item < 0)
                    new_item = e.mb_ino[MBI_HELP];
                e.menubar->pull_down(new_item);
                RedrawMap = 1;
            }

            else if (is.key == YK_RIGHT && e.menubar->highlighted() >= 0)
            {
                int new_item = e.menubar->highlighted() + 1;
                if (new_item > e.mb_ino[MBI_HELP])
                    new_item = 0;
                e.menubar->pull_down(new_item);
                RedrawMap = 1;
            }

            else if (is.key == YK_ALT + 'f')
                e.menubar->pull_down(e.mb_ino[MBI_FILE]);

            else if (is.key == YK_ALT + 'e')
                e.menubar->pull_down(e.mb_ino[MBI_EDIT]);

            else if (is.key == YK_ALT + 's')
                e.menubar->pull_down(e.mb_ino[MBI_SEARCH]);

            else if (is.key == YK_ALT + 'v')
                e.menubar->pull_down(e.mb_ino[MBI_VIEW]);

            else if (is.key == YK_ALT + 'm')
                e.menubar->pull_down(e.mb_ino[MBI_MISC]);

            else if (is.key == YK_ALT + 'o')
                e.menubar->pull_down(e.mb_ino[MBI_OBJECTS]);

            else if (is.key == YK_ALT + 'c')
                e.menubar->pull_down(e.mb_ino[MBI_CHECK]);

            else if (is.key == YK_ALT + 'h')
                e.menubar->pull_down(e.mb_ino[MBI_HELP]);


            // [Ctrl][L]: force redraw
            else if (is.key == '\f')
            {
                RedrawMap = 1;
            }

            // [Esc], [q]: close
            else if (is.key == YK_ESC || is.key == 'q')
            {
                if (DragObject)
                    DragObject = false;
                else if (StretchSelBox)
                    StretchSelBox = false;
                else
                {
                    ForgetSelection(&e.Selected);
                    if (!MadeChanges
                        || Confirm(-1, -1, "You have unsaved changes."
                                   " Do you really want to quit?", 0))
                        break;
                    RedrawMap = 1;
                }
            }

            // [F1]: pop up "Help" window
            else if (is.key == YK_F1)        /* 'F1' */
            {
                DisplayHelp();
                RedrawMap = 1;
            }

            // [Alt][a]: pop up the "About..." window
            else if (is.key == YK_ALT + 'a')
            {
                about_yadex();
                RedrawMap = 1;
            }

            // [Shift][F1]: save a screen shot into yadex.gif.
            // FIXME doesn't work in the Unix port
            else if (is.key == YK_F1 + YK_SHIFT)
            {
                Rgbbmp b;
                window_to_rgbbmp(0, 0, (int) cfg.ScrMaxX + 1, (int) cfg.ScrMaxY + 1,
                                 b);
                rgbbmp_to_rawppm(b, "yadex.ppm");
                //ScreenShot ();
            }

            // [Shift][F2]: undocumented--test of Entry2
            else if (is.key == YK_F2 + YK_SHIFT)
            {
                char buf1[10];
                char buf2[30];
                char buf3[20];
                strcpy(buf1, "buf1");
                strcpy(buf2, "buf2");
                strcpy(buf3, "buf3");
                Entry2 e("Title of window", "Buf 1%*sBuf 2%*sBuf 3%*s",
                         sizeof buf1 - 1, buf1,
                         sizeof buf2 - 1, buf2, sizeof buf3 - 1, buf3);
                e.loop();
                printf("bufs: \"%s\", \"%s\", \"%s\"\n", buf1, buf2, buf3);
                RedrawMap = 1;
            }

            /* [F2] save level into pwad, prompt for the file name
               every time but keep the same level name. */
            else if (is.key == YK_F2 && cfg.Registered)
            {
                if (!CheckStartingPos())
                    goto cancel_save;
                char *outfile;
                const char *newlevelname;
                if (levelname)
                    newlevelname = levelname;
                else
                {
                    newlevelname = SelectLevel(0);
                    if (!*newlevelname)
                        goto cancel_save;
                }
                outfile = GetWadFileName(newlevelname);
                if (!outfile)
                    goto cancel_save;
                SaveLevelData(outfile, newlevelname);
                levelname = newlevelname;
                // Sigh. Shouldn't have to do that. Level must die !
                Level = FindMasterDir(cfg.MasterDir, levelname);
              cancel_save:
                RedrawMap = 1;
            }

            /* [F3] save level into pwad, prompt for the file name and
               level name. */
            else if (is.key == YK_F3 && cfg.Registered)
            {
                char *outfile;
                const char *newlevelname;
                MDirPtr newLevel, oldl, newl;

                if (!CheckStartingPos())
                    goto cancel_save_as;
                newlevelname = SelectLevel(0);
                if (!*newlevelname)
                    goto cancel_save_as;
                if (!levelname || y_stricmp(newlevelname, levelname))
                {
                    /* horrible but it works... */
                    // Horrible indeed -- AYM 1999-07-30
                    newLevel = FindMasterDir(cfg.MasterDir, newlevelname);
                    if (!newLevel)
                        nf_bug("newLevel is NULL");        // Debatable ! -- AYM 2001-05-29
                    if (Level)        // If new level ("create" command), Level is NULL
                    {
                        oldl = Level;
                        newl = newLevel;
                        for (int m = 0; m < 11; m++)
                        {
                            newl->wadfile = oldl->wadfile;
                            if (m > 0)
                                newl->dir = oldl->dir;
                            /*
                               if (!fncmp (outfile, oldl->wadfile->filename))
                               {
                               oldl->wadfile = WadFileList;
                               oldl->dir = lost...
                               }
                             */
                            oldl = oldl->next;
                            newl = newl->next;
                        }
                    }
                    Level = newLevel;
                }
                outfile = GetWadFileName(newlevelname);
                if (!outfile)
                    goto cancel_save_as;
                SaveLevelData(outfile, newlevelname);
                levelname = newlevelname;
              cancel_save_as:
                RedrawMap = 1;
            }

            // [F5]: pop up the "Preferences" menu
            else if (is.key == YK_F5 && e.menubar->highlighted() < 0)
            {
                Preferences(-1, -1);
                RedrawMap = 1;
            }

            // [a]: pop up the "Set flag" menu
            else if (is.key == 'a'
                     && e.menubar->highlighted() < 0
                     && (e.Selected || e.highlighted()))
            {
                e.modal = 's';        // Set
                if (e.obj_type == OBJ_LINEDEFS)
                {
                    menu_linedef_flags->set_title("Set linedef flag");
                    e.modpopup->set(menu_linedef_flags, 0);
                }
                else if (e.obj_type == OBJ_THINGS)
                {
                    menu_thing_flags->set_title("Set thing flag");
                    e.modpopup->set(menu_thing_flags, 0);
                }
            }

            // [b]: pop up the "Toggle flag" menu
            else if (is.key == 'b'
                     && e.menubar->highlighted() < 0
                     && (e.Selected || e.highlighted()))
            {
                e.modal = 't';        // Toggle
                if (e.obj_type == OBJ_LINEDEFS)
                {
                    menu_linedef_flags->set_title("Toggle linedef flag");
                    e.modpopup->set(menu_linedef_flags, 0);
                }
                else if (e.obj_type == OBJ_THINGS)
                {
                    menu_thing_flags->set_title("Toggle thing flag");
                    e.modpopup->set(menu_thing_flags, 0);
                }
            }

            // [c]: pop up the "Clear flag" menu
            else if (is.key == 'c'
                     && e.menubar->highlighted() < 0
                     && (e.Selected || e.highlighted()))
            {
                e.modal = 'c';        // Clear;
                if (e.obj_type == OBJ_LINEDEFS)
                {
                    menu_linedef_flags->set_title("Clear linedef flag");
                    e.modpopup->set(menu_linedef_flags, 0);
                }
                else if (e.obj_type == OBJ_THINGS)
                {
                    menu_thing_flags->set_title("Clear thing flag");
                    e.modpopup->set(menu_thing_flags, 0);
                }
            }

            // [F8]: pop up the "Misc. operations" menu
            else if (is.key == YK_F8 && e.menubar->highlighted() < 0)
            {
                e.modpopup->set(e.menubar->get_menu(MBI_MISC), 1);
            }

            // [F9]: pop up the "Insert a standard object" menu
            else if (is.key == YK_F9 && e.menubar->highlighted() < 0)
            {
                e.modpopup->set(e.menubar->get_menu(MBI_OBJECTS), 1);
            }

            // [F10]: pop up the "Checks" menu
            else if (is.key == YK_F10 && e.menubar->highlighted() < 0)
            {
                CheckLevel(-1, -1);
                RedrawMap = 1;
            }

            // [Alt][i]: show/hide the info bar
            else if (is.key == YK_ALT + 'i')
            {
                e.infobar_shown = !e.infobar_shown;
                RedrawMap = 1;
            }

            // [i]: show/hide the object info boxes
            else if (is.key == 'i')
            {
                e.objinfo_shown = !e.objinfo_shown;
                RedrawMap = 1;
            }

            // [+], [=], wheel: zooming in
            else if (is.key == '+' || is.key == '=' || is.key == YE_WHEEL_UP)
            {
                int r = edit_zoom_in(&e);
                if (r == 0)
                    RedrawMap = 1;
            }

            // [-], [_], wheel: zooming out
            else if (is.key == '-' || is.key == '_'
                     || is.key == YE_WHEEL_DOWN)
            {
                int r = edit_zoom_out(&e);
                if (r == 0)
                    RedrawMap = 1;
            }

            // [1] - [9], [0]: set the zoom factor
            else if (is.key >= '0' && is.key <= '9')
            {
                int r = edit_set_zoom(&e, digit_zoom_factors[dectoi(is.key)]);
                if (r == 0)
                    RedrawMap = 1;
            }

            // [']: centre window on centre of map
            else if (is.key == '\'')
            {
                update_level_bounds();
                CenterMapAroundCoords((MapMinX + MapMaxX) / 2,
                                      (MapMinY + MapMaxY) / 2);
                RedrawMap = 1;
            }

            // [`]: centre window on centre of map
            // and set zoom to view the entire map
            else if (is.key == '`')
            {
                int r = zoom_fit(e);
                if (r == 0)
                    RedrawMap = 1;
            }


            // [Left], [Right], [Up], [Down]:
            // scroll <cfg.scroll_less> percents of a screenful.
            else if (is.key == YK_LEFT && MAPX(cfg.ScrCenterX) > -20000)
            {
                cfg.OrigX -= (int) ((double) cfg.ScrMaxX * cfg.scroll_less / 100 / cfg.Scale);
                RedrawMap = 1;
            }
            else if (is.key == YK_RIGHT && MAPX(cfg.ScrCenterX) < 20000)
            {
                cfg.OrigX += (int) ((double) cfg.ScrMaxX * cfg.scroll_less / 100 / cfg.Scale);
                RedrawMap = 1;
            }
            else if (is.key == YK_UP && MAPY(cfg.ScrCenterY) < 20000)
            {
                cfg.OrigY += (int) ((double) cfg.ScrMaxY * cfg.scroll_less / 100 / cfg.Scale);
                RedrawMap = 1;
            }
            else if (is.key == YK_DOWN && MAPY(cfg.ScrCenterY) > -20000)
            {
                cfg.OrigY -= (int) ((double) cfg.ScrMaxY * cfg.scroll_less / 100 / cfg.Scale);
                RedrawMap = 1;
            }

            // [Pgup], [Pgdn], [Home], [End]:
            // scroll <cfg.scroll_more> percents of a screenful.
            else if (is.key == YK_PU && MAPY(cfg.ScrCenterY) < /*MapMaxY */ 20000)
            {
                cfg.OrigY += (int) ((double) cfg.ScrMaxY * cfg.scroll_more / 100 / cfg.Scale);
                RedrawMap = 1;
            }
            else if (is.key == YK_PD
                     && MAPY(cfg.ScrCenterY) > /*MapMinY */ -20000)
            {
                cfg.OrigY -= (int) ((double) cfg.ScrMaxY * cfg.scroll_more / 100 / cfg.Scale);
                RedrawMap = 1;
            }
            else if (is.key == YK_HOME
                     && MAPX(cfg.ScrCenterX) > /*MapMinX */ -20000)
            {
                cfg.OrigX -= (int) ((double) cfg.ScrMaxX * cfg.scroll_more / 100 / cfg.Scale);
                RedrawMap = 1;
            }
            else if (is.key == YK_END
                     && MAPX(cfg.ScrCenterX) < /*MapMaxX */ 20000)
            {
                cfg.OrigX += (int) ((double) cfg.ScrMaxX * cfg.scroll_more / 100 / cfg.Scale);
                RedrawMap = 1;
            }

            else if (is.key == ' ')
            {
                e.extra_zoom = !e.extra_zoom;
                edit_set_zoom(&e, cfg.Scale * (e.extra_zoom ? 4 : 0.25));
                RedrawMap = 1;
            }

            // [Tab], [l], [s], [t], [v]: switch mode
            else if (is.key == YK_TAB || is.key == YK_BACKTAB
                     || is.key == 't' || is.key == 'v' || is.key == 'l'
                     || is.key == 's')
            {
                int old_mode;
                int new_mode = -1;
                int PrevMode = e.obj_type;
                SelPtr NewSel;

                // What's the number of the current mode ?
                old_mode = obj_type_to_mode_no(e.obj_type);

                // What's the number of the new mode ?
                if (is.key == YK_TAB)        // [Tab]
                    new_mode = (old_mode + 1) % NB_MODES;
                else if (is.key == YK_BACKTAB)        // [Shift]-[Tab]
                    new_mode = old_mode == 0 ? NB_MODES - 1 : old_mode - 1;
                else
                {
                    if (is.key == 't')
                        new_mode = obj_type_to_mode_no(OBJ_THINGS);
                    else if (is.key == 'v')
                        new_mode = obj_type_to_mode_no(OBJ_VERTICES);
                    else if (is.key == 'l')
                        new_mode = obj_type_to_mode_no(OBJ_LINEDEFS);
                    else if (is.key == 's')
                        new_mode = obj_type_to_mode_no(OBJ_SECTORS);
                    else
                        fatal_error("changing mode with %04X", is.key);
                    /* unselect all */
                    ForgetSelection(&e.Selected);
                }

                // Set the object type according to the new mode.
                e.obj_type = modes[new_mode].obj_type;

                // Change the flavour of the "Misc" menu.
                e.menubar->set_menu(e.mb_ino[MBI_MISC],
                                    e.mb_menu[modes[new_mode].menu_no]);

                /* special cases for the selection list... */
                if (e.Selected)
                {
                    /* select all linedefs bound to the selected sectors */
                    if (PrevMode == OBJ_SECTORS && e.obj_type == OBJ_LINEDEFS)
                    {
                        int l, sd;

                        NewSel = 0;
                        for (l = 0; l < NumLineDefs; l++)
                        {
                            sd = LineDefs[l].sidedef1;
                            if (sd >= 0
                                && IsSelected(e.Selected,
                                              SideDefs[sd].sector))
                                SelectObject(&NewSel, l);
                            else
                            {
                                sd = LineDefs[l].sidedef2;
                                if (sd >= 0
                                    && IsSelected(e.Selected,
                                                  SideDefs[sd].sector))
                                    SelectObject(&NewSel, l);
                            }
                        }
                        ForgetSelection(&e.Selected);
                        e.Selected = NewSel;
                    }
                    /* select all Vertices bound to the selected linedefs */
                    else if (PrevMode == OBJ_LINEDEFS
                             && e.obj_type == OBJ_VERTICES)
                    {
                        NewSel = 0;
                        while (e.Selected)
                        {
                            if (!IsSelected
                                (NewSel, LineDefs[e.Selected->objnum].start))
                                SelectObject(&NewSel,
                                             LineDefs[e.Selected->objnum].
                                             start);
                            if (!IsSelected
                                (NewSel, LineDefs[e.Selected->objnum].end))
                                SelectObject(&NewSel,
                                             LineDefs[e.Selected->objnum].
                                             end);
                            UnSelectObject(&e.Selected, e.Selected->objnum);
                        }
                        e.Selected = NewSel;
                    }
                    /* select all sectors that have their linedefs selected */
                    else if (PrevMode == OBJ_LINEDEFS
                             && e.obj_type == OBJ_SECTORS)
                    {
                        int l, sd;

                        NewSel = 0;
                        /* select all sectors... */
                        for (l = 0; l < NumSectors; l++)
                            SelectObject(&NewSel, l);
                        /* ... then unselect those that should not be in the list */
                        for (l = 0; l < NumLineDefs; l++)
                            if (!IsSelected(e.Selected, l))
                            {
                                sd = LineDefs[l].sidedef1;
                                if (sd >= 0)
                                    UnSelectObject(&NewSel,
                                                   SideDefs[sd].sector);
                                sd = LineDefs[l].sidedef2;
                                if (sd >= 0)
                                    UnSelectObject(&NewSel,
                                                   SideDefs[sd].sector);
                            }
                        ForgetSelection(&e.Selected);
                        e.Selected = NewSel;
                    }
                    /* select all linedefs that have both ends selected */
                    else if (PrevMode == OBJ_VERTICES
                             && e.obj_type == OBJ_LINEDEFS)
                    {
                        int l;

                        NewSel = 0;
                        for (l = 0; l < NumLineDefs; l++)
                            if (IsSelected(e.Selected, LineDefs[l].start)
                                && IsSelected(e.Selected, LineDefs[l].end))
                                SelectObject(&NewSel, l);
                        ForgetSelection(&e.Selected);
                        e.Selected = NewSel;
                    }
                    /* unselect all */
                    else
                        ForgetSelection(&e.Selected);
                }
                if (GetMaxObjectNum(e.obj_type) >= 0 && cfg.Select0 && !e.global)
                {
                    e.highlighted.type = e.obj_type;
                    e.highlighted.num = 0;
                }
                else
                    e.highlighted.nil();

                DragObject = false;
                StretchSelBox = false;
                RedrawMap = 1;
            }

            // [Ctrl][g]: toggle global mode
            else if (is.key == '\7')
            {
                static bool dont_warn = false;
                bool ok = false;
                if (e.global)
                    ok = true;        // No confirmation needed to switch off
                else if (dont_warn)
                    ok = true;
                else
                {
                    ok = Confirm(-1, -1,
                                 "Global mode is experimental and probably highly",
                                 "unstable. This means crashes. Are you sure ?");
                    RedrawMap = 1;
                    if (ok)
                    {
                        dont_warn = true;        // User is sure. Won't ask again
                        Notify(-1, -1,
                               "Selection does not work in global mode. Don't",
                               "bother reporting it, I'm aware of it already.");
                    }
                }
                if (ok)
                {
                    ForgetSelection(&e.Selected);
                    e.global = !e.global;
                    RedrawMap = 1;
                }
            }

            // [e]: Select/unselect all linedefs in non-forked path
            else if (is.key == 'e' && e.highlighted._is_linedef())
            {
                ForgetSelection(&e.Selected);
                select_linedefs_path(&e.Selected, e.highlighted.num, YS_ADD);
                RedrawMap = 1;
            }

            // [Ctrl][e] Select/unselect all linedefs in path
            else if (is.key == '\5' && !is.shift
                     && e.highlighted._is_linedef())
            {
                select_linedefs_path(&e.Selected, e.highlighted.num,
                                     YS_TOGGLE);
                RedrawMap = 1;
            }
            // [E]: add linedef and split sector -- [AJA]
            else if (is.key == 'E' && e.obj_type == OBJ_VERTICES)
            {
                if (e.Selected)
                {
                    MiscOperations(e.obj_type, &e.Selected, 5);
                    RedrawMap = 1;
                }
            }
            // [E]: Select/unselect all 1s linedefs in path
            else if (is.key == 'E' && e.highlighted._is_linedef())
            {
                ForgetSelection(&e.Selected);
                select_1s_linedefs_path(&e.Selected, e.highlighted.num,
                                        YS_ADD);
                RedrawMap = 1;
            }

            // [Ctrl][Shift][e]: Select/unselect all 1s linedefs in path
            else if (is.key == '\5' && is.shift
                     && e.highlighted._is_linedef())
            {
                select_1s_linedefs_path(&e.Selected, e.highlighted.num,
                                        YS_TOGGLE);
                RedrawMap = 1;
            }

            // [G]: to increase the grid step
            else if (is.key == 'G')
            {
                if (e.grid_step < e.grid_step_max)
                    e.grid_step *= 2;
                else
                    e.grid_step = e.grid_step_min;
                RedrawMap = 1;
            }

            // [g]: decrease the grid step
            else if (is.key == 'g')
            {
                if (e.grid_step > e.grid_step_min)
                    e.grid_step /= 2;
                else
                    e.grid_step = e.grid_step_max;
                RedrawMap = 1;
            }

            // [h]: display or hide the grid
            else if (is.key == 'h')
            {
                e.grid_shown = !e.grid_shown;
                RedrawMap = 1;
            }

            // [H]: reset the grid to grid_step_max
            else if (is.key == 'H')
            {
                e.grid_step = e.grid_step_max;
                RedrawMap = 1;
            }

            // [y]: toggle the snap_to_grid flag
            else if (is.key == 'y')
            {
                e.grid_snap = !e.grid_snap;
            }

            // [z]: toggle the lock_grip_step flag
            else if (is.key == 'z')
            {
                e.grid_step_locked = !e.grid_step_locked;
            }

            // [r]: toggle the rulers
            else if (is.key == 'r')
                e.rulers_shown = !e.rulers_shown;

            // [n], [>]: highlight the next object
            else if ((is.key == 'n' || is.key == '>')
                     && (!e.global ||e.highlighted()))
            {
                obj_type_t t =
                    e.highlighted()? e.highlighted.type : e.obj_type;
                obj_no_t nmax = GetMaxObjectNum(t);
                if (is_obj(nmax))
                {
                    if (e.highlighted.is_nil())
                    {
                        e.highlighted.type = t;
                        e.highlighted.num = 0;
                    }
                    else
                    {
                        e.highlighted.num++;
                        if (e.highlighted.num > nmax)
                            e.highlighted.num = 0;
                    }
                    GoToObject(e.highlighted);
                    RedrawMap = 1;
                }
            }

            // [p], [<]: highlight the previous object
            else if ((is.key == 'p' || is.key == '<')
                     && (!e.global ||e.highlighted()))
            {
                obj_type_t t =
                    e.highlighted()? e.highlighted.type : e.obj_type;
                obj_no_t nmax = GetMaxObjectNum(t);
                if (is_obj(nmax))
                {
                    if (e.highlighted.is_nil())
                    {
                        e.highlighted.type = t;
                        e.highlighted.num = nmax;
                    }
                    else
                    {
                        e.highlighted.num--;
                        if (e.highlighted.num < 0)
                            e.highlighted.num = nmax;
                    }
                    GoToObject(e.highlighted);
                    RedrawMap = 1;
                }
            }

            // [j], [#]: jump to object by number
            else if ((is.key == 'j' || is.key == '#')
                     && (!e.global ||e.highlighted()))
            {
                Objid default_obj;
                default_obj.type =
                    e.highlighted()? e.highlighted.type : e.obj_type;
                default_obj.num = e.highlighted()? e.highlighted.num : 0;
                Objid target_obj;
                input_objid(target_obj, default_obj, -1, -1);
                if (target_obj())
                    GoToObject(target_obj);
                RedrawMap = 1;
            }

            // [f]: find object by type
            else if (is.key == 'f' && (!e.global ||e.highlighted()))
            {
                Objid find_obj;
                int otype;
                obj_no_t omax, onum;
                find_obj.type =
                    e.highlighted()? e.highlighted.type : e.obj_type;
                onum = find_obj.num = e.highlighted()? e.highlighted.num : 0;
                omax = GetMaxObjectNum(find_obj.type);
                switch (find_obj.type)
                {
                case OBJ_SECTORS:
                    if (!InputSectorType(84, 21, &otype))
                    {
                        for (onum = e.highlighted()? onum + 1 : onum;
                             onum <= omax; onum++)
                            if (Sectors[onum].special == (wad_stype_t) otype)
                            {
                                find_obj.num = onum;
                                GoToObject(find_obj);
                                break;
                            }
                    }
                    break;
                case OBJ_THINGS:
                    if (!InputThingType(42, 21, &otype))
                    {
                        for (onum = e.highlighted()? onum + 1 : onum;
                             onum <= omax; onum++)
                            if (Things[onum].type == (wad_ttype_t) otype)
                            {
                                find_obj.num = onum;
                                GoToObject(find_obj);
                                break;
                            }
                    }
                    break;
                case OBJ_LINEDEFS:
                    if (!InputLinedefType(0, 21, &otype))
                    {
                        for (onum = e.highlighted()? onum + 1 : onum;
                             onum <= omax; onum++)
                            if (LineDefs[onum].type == (wad_ldtype_t) otype)
                            {
                                find_obj.num = onum;
                                GoToObject(find_obj);
                                break;
                            }
                    }
                    break;
                }
                RedrawMap = 1;
            }
#if 0
            // [c]: clear selection and redraw the map
            else if (is.key == 'c')
            {
                ForgetSelection(&e.Selected);
                RedrawMap = 1;
                DragObject = false;
                StretchSelBox = false;
            }
#endif

            // [o]: copy a group of objects
            else if (is.key == 'o' && (e.Selected || e.highlighted()))
            {
                int x, y;

                /* copy the object(s) */
                if (!e.Selected)
                    SelectObject(&e.Selected, e.highlighted.num);
                CopyObjects(e.obj_type, e.Selected);
                /* enter drag mode */
                //DragObject = true;
                /* AYM 19980619 : got to look into this!! */
                //e.highlight_obj_no = e.Selected->objnum;

                // Find the "hotspot" in the object(s)
                if (e.highlighted() && !e.Selected)
                    GetObjectCoords(e.highlighted.type, e.highlighted.num, &x,
                                    &y);
                else
                    centre_of_objects(e.obj_type, e.Selected, &x, &y);

                // Drag the object(s) so that the "hotspot" is under the pointer
                MoveObjectsToCoords(e.obj_type, 0, x, y, 0);
                MoveObjectsToCoords(e.obj_type, e.Selected,
                                    e.pointer_x, e.pointer_y, 0);
                RedrawMap = 1;
                StretchSelBox = false;
            }

            // [Return]: edit the properties of the current object.
            else if (is.key == YK_RETURN && (e.Selected || e.highlighted()))
            {
                if (e.Selected)
                    EditObjectsInfo(0, menubar_out_y1 + 1, e.obj_type,
                                    e.Selected);
                else
                {
                    SelectObject(&e.Selected, e.highlighted.num);
                    EditObjectsInfo(0, menubar_out_y1 + 1, e.highlighted.type,
                                    e.Selected);
                    UnSelectObject(&e.Selected, e.highlighted.num);
                }
                RedrawMap = 1;
                DragObject = false;
                StretchSelBox = false;
            }

            // [w]: spin things 1/8 turn counter-clockwise
            else if (is.key == 'w' && e.obj_type == OBJ_THINGS
                     && (e.Selected || e.highlighted()))
            {
                if (!e.Selected)
                {
                    SelectObject(&e.Selected, e.highlighted.num);
                    spin_things(e.Selected, 45);
                    UnSelectObject(&e.Selected, e.highlighted.num);
                }
                else
                {
                    spin_things(e.Selected, 45);
                }
                RedrawMap = 1;        /* FIXME: should redraw only the things */
                DragObject = false;
                StretchSelBox = false;
            }

            // [w]: split linedefs and sectors
            else if (is.key == 'w' && e.obj_type == OBJ_LINEDEFS
                     && e.Selected && e.Selected->next
                     && !e.Selected->next->next)
            {
                SplitLineDefsAndSector(e.Selected->next->objnum,
                                       e.Selected->objnum);
                ForgetSelection(&e.Selected);
                RedrawMap = 1;
                DragObject = false;
                StretchSelBox = false;
            }

            // [w]: split sector between vertices
            else if (is.key == 'w' && e.obj_type == OBJ_VERTICES
                     && e.Selected && e.Selected->next
                     && !e.Selected->next->next)
            {
                SplitSector(e.Selected->next->objnum, e.Selected->objnum);
                ForgetSelection(&e.Selected);
                RedrawMap = 1;
                DragObject = false;
                StretchSelBox = false;
            }

            // [x]: spin things 1/8 turn clockwise
            else if (is.key == 'x' && e.obj_type == OBJ_THINGS
                     && (e.Selected || e.highlighted()))
            {
                if (!e.Selected)
                {
                    SelectObject(&e.Selected, e.highlighted.num);
                    spin_things(e.Selected, -45);
                    UnSelectObject(&e.Selected, e.highlighted.num);
                }
                else
                {
                    spin_things(e.Selected, -45);
                }
                RedrawMap = 1;        /* FIXME: should redraw only the things */
                DragObject = false;
                StretchSelBox = false;
            }

            // [x]: split linedefs
            else if (is.key == 'x' && e.obj_type == OBJ_LINEDEFS
                     && (e.Selected || e.highlighted()))
            {
                if (!e.Selected)
                {
                    SelectObject(&e.Selected, e.highlighted.num);
                    SplitLineDefs(e.Selected);
                    UnSelectObject(&e.Selected, e.highlighted.num);
                }
                else
                    SplitLineDefs(e.Selected);
                RedrawMap = 1;
                DragObject = false;
                StretchSelBox = false;
            }

            // [Ctrl][x]: exchange objects numbers
            else if (is.key == 24)
            {
                if (!e.Selected
                    || !e.Selected->next || (e.Selected->next)->next)
                {
                    Beep();
                    Notify(-1, -1, "You must select exactly two objects", 0);
                    RedrawMap = 1;
                }
                else
                {
                    exchange_objects_numbers(e.obj_type, e.Selected, true);
                    RedrawMap = 1;
                }
            }

            // [Ctrl][k]: cut a slice out of a sector
            else if (is.key == 11 && e.obj_type == OBJ_LINEDEFS
                     && e.Selected && e.Selected->next
                     && !e.Selected->next->next)
            {
                sector_slice(e.Selected->next->objnum, e.Selected->objnum);
                ForgetSelection(&e.Selected);
                RedrawMap = 1;
                DragObject = false;
                StretchSelBox = false;
            }

            // [Del]: delete the current object
            else if (is.key == YK_DEL && (e.Selected || e.highlighted()))        /* 'Del' */
            {
                if (e.obj_type == OBJ_THINGS
                    || cfg.Expert
                    || Confirm(-1, -1,
                               (e.Selected && e.Selected->next ?
                                "Do you really want to delete these objects?"
                                :
                                "Do you really want to delete this object?"),
                               (e.Selected
                                && e.Selected->
                                next ?
                                "This will also delete the objects bound to them."
                                :
                                "This will also delete the objects bound to it.")))
                {
                    if (e.Selected)
                        DeleteObjects(e.obj_type, &e.Selected);
                    else
                        DeleteObject(e.highlighted);
                }
                // AYM 1998-09-20 I thought I'd add this
                // (though it doesn't fix the problem : if the object has been
                // deleted, HighlightObject is still called with a bad object#).
                e.highlighted.nil();
                DragObject = false;
                StretchSelBox = false;
                RedrawMap = 1;
            }

            // [Ins]: insert a new object
            else if (is.key == YK_INS || is.key == YK_INS + YK_SHIFT)        /* 'Ins' */
            {
                SelPtr cur;
                int prev_obj_type = e.obj_type;

                /* first special case: if several vertices are
                   selected, add new linedefs */
                if (e.obj_type == OBJ_VERTICES
                    && e.Selected && e.Selected->next)
                {
                    int firstv;
                    int obj_no = OBJ_NO_NONE;

                    if (e.Selected->next->next)
                        firstv = e.Selected->objnum;
                    else
                        firstv = -1;
                    e.obj_type = OBJ_LINEDEFS;
                    /* create linedefs between the vertices */
                    for (cur = e.Selected; cur->next; cur = cur->next)
                    {
                        /* check if there is already a linedef between the two vertices */
                        for (obj_no = 0; obj_no < NumLineDefs; obj_no++)
                            if ((LineDefs[obj_no].start == cur->next->objnum
                                 && LineDefs[obj_no].end == cur->objnum)
                                || (LineDefs[obj_no].end == cur->next->objnum
                                    && LineDefs[obj_no].start == cur->objnum))
                                break;
                        if (obj_no < NumLineDefs)
                            cur->objnum = obj_no;
                        else
                        {
                            InsertObject(OBJ_LINEDEFS, -1, 0, 0);
                            e.highlighted.type = OBJ_LINEDEFS;
                            e.highlighted.num = NumLineDefs - 1;
                            LineDefs[e.highlighted.num].start =
                                cur->next->objnum;
                            LineDefs[e.highlighted.num].end = cur->objnum;
                            cur->objnum = e.highlighted.num;        // FIXME cur = e.highlighted
                        }
                    }
                    /* close the polygon if there are more than 2 vertices */
                    if (firstv >= 0 && is.shift)
                    {
                        e.highlighted.type = OBJ_LINEDEFS;
                        for (e.highlighted.num = 0;
                             e.highlighted.num < NumLineDefs;
                             e.highlighted.num++)
                            if ((LineDefs[e.highlighted.num].start == firstv
                                 && LineDefs[e.highlighted.num].end ==
                                 cur->objnum)
                                || (LineDefs[e.highlighted.num].end == firstv
                                    && LineDefs[e.highlighted.num].start ==
                                    cur->objnum))
                                break;
                        if (e.highlighted.num < NumLineDefs)
                            cur->objnum = obj_no;
                        else
                        {
                            InsertObject(OBJ_LINEDEFS, -1, 0, 0);
                            e.highlighted.type = OBJ_LINEDEFS;
                            e.highlighted.num = NumLineDefs - 1;
                            LineDefs[e.highlighted.num].start = firstv;
                            LineDefs[e.highlighted.num].end = cur->objnum;
                            cur->objnum = e.highlighted.num;        // FIXME cur = e.highlighted
                        }
                    }
                    else
                        UnSelectObject(&e.Selected, cur->objnum);
                }
                /* second special case: if several linedefs are selected,
                   add new sidedefs and one sector */
                else if (e.obj_type == OBJ_LINEDEFS && e.Selected)
                {
                    for (cur = e.Selected; cur; cur = cur->next)
                        if (LineDefs[cur->objnum].sidedef1 >= 0
                            && LineDefs[cur->objnum].sidedef2 >= 0)
                        {
                            char msg[80];

                            Beep();
                            sprintf(msg,
                                    "Linedef #%d already has two sidedefs",
                                    cur->objnum);
                            Notify(-1, -1, "Error: cannot add the new sector",
                                   msg);
                            break;
                        }
                    if (!cur)
                    {
                        e.obj_type = OBJ_SECTORS;
                        InsertObject(OBJ_SECTORS, -1, 0, 0);
                        e.highlighted.type = OBJ_SECTORS;
                        e.highlighted.num = NumSectors - 1;
                        for (cur = e.Selected; cur; cur = cur->next)
                        {
                            InsertObject(OBJ_SIDEDEFS, -1, 0, 0);
                            SideDefs[NumSideDefs - 1].sector =
                                e.highlighted.num;
                            if (LineDefs[cur->objnum].sidedef1 >= 0)
                            {
                                int s;

                                s = SideDefs[LineDefs[cur->objnum].sidedef1].
                                    sector;
                                if (s >= 0)
                                {
                                    Sectors[e.highlighted.num].floorh =
                                        Sectors[s].floorh;
                                    Sectors[e.highlighted.num].ceilh =
                                        Sectors[s].ceilh;
                                    strncpy(Sectors[e.highlighted.num].floort,
                                            Sectors[s].floort, WAD_FLAT_NAME);
                                    strncpy(Sectors[e.highlighted.num].ceilt,
                                            Sectors[s].ceilt, WAD_FLAT_NAME);
                                    Sectors[e.highlighted.num].light =
                                        Sectors[s].light;
                                }
                                LineDefs[cur->objnum].sidedef2 =
                                    NumSideDefs - 1;
                                LineDefs[cur->objnum].flags = 4;
                                strncpy(SideDefs[NumSideDefs - 1].tex3,
                                        "-", WAD_TEX_NAME);
                                strncpy(SideDefs
                                        [LineDefs[cur->objnum].sidedef1].tex3,
                                        "-", WAD_TEX_NAME);
                            }
                            else
                                LineDefs[cur->objnum].sidedef1 =
                                    NumSideDefs - 1;
                        }
                        ForgetSelection(&e.Selected);
                        SelectObject(&e.Selected, e.highlighted.num);
                    }
                }
                /* normal case: add a new object of the current type */
                else
                {
                    ForgetSelection(&e.Selected);
                    /* FIXME how do you insert a new object of type T if
                       no object of that type already exists ? */
                    obj_type_t t =
                        e.highlighted()? e.highlighted.type : e.obj_type;
                    InsertObject(t, e.highlighted.num,
                                 edit_mapx_snapped(&e, e.pointer_x),
                                 edit_mapy_snapped(&e, e.pointer_y));
                    e.highlighted.type = t;
                    e.highlighted.num = GetMaxObjectNum(e.obj_type);
                    if (e.obj_type == OBJ_LINEDEFS)
                    {
                        int v1 = LineDefs[e.highlighted.num].start;
                        int v2 = LineDefs[e.highlighted.num].end;
                        if (!Input2VertexNumbers(-1, -1,
                                                 "Choose the two vertices for the new linedef",
                                                 &v1, &v2))
                        {
                            DeleteObject(e.highlighted);
                            e.highlighted.nil();
                        }
                        else
                        {
                            LineDefs[e.highlighted.num].start = v1;
                            LineDefs[e.highlighted.num].end = v2;
                        }
                    }
                    else if (e.obj_type == OBJ_VERTICES)
                    {
                        SelectObject(&e.Selected, e.highlighted.num);
                        if (AutoMergeVertices(&e.Selected, e.obj_type, 'i'))
                            RedrawMap = 1;
                        ForgetSelection(&e.Selected);
                    }
                }

                // Mode-changing code, duplicated from above.
                // As RQ would say: "I hate to do that". So do I.
                // The best solution would be to have a mode
                // changing function that only changes e.obj_type
                // and emits a "mode change" message. Another part
                // of the code, responsible for the menus, would
                // intercept that message and update them. I don't
                // have the time (as of 1998-12-14) to do it now.
                if (e.obj_type != prev_obj_type)
                {
                    int new_mode;

                    // What's the number of the new mode ?
                    new_mode = obj_type_to_mode_no(e.obj_type);

                    // Change the flavour of the "Misc" menu.
                    e.menubar->set_menu(e.mb_ino[MBI_MISC],
                                        e.mb_menu[modes[new_mode].menu_no]);
                }

                DragObject = false;
                StretchSelBox = false;
                RedrawMap = 1;
            }

            // [Z] Set sector on surrounding linedefs (AJA)
            else if (is.key == 'Z' && e.pointer_in_window)
            {
                if (e.obj_type == OBJ_SECTORS && e.Selected)
                {
                    SuperSectorSelector(e.pointer_x, e.pointer_y,
                                        e.Selected->objnum);
                }
                else
                {
                    SuperSectorSelector(e.pointer_x, e.pointer_y,
                                        OBJ_NO_NONE);
                }
                RedrawMap = 1;
            }

            // [!] Debug info (not documented)
            else if (is.key == '!')
            {
                DumpSelection(e.Selected);
            }

            // [R] Render 3D view (AJA)
            else if (is.key == 'R')
            {
                Render3D();
                RedrawMap = 1;
            }

            // [T] Transfer properties to selected objects (AJA)
            else if (is.key == 'T' && e.Selected && e.highlighted.num >= 0)
            {
                switch (e.obj_type)
                {
                case OBJ_SECTORS:
                    TransferSectorProperties(e.highlighted.num, e.Selected);
                    RedrawMap = 1;
                    break;
                case OBJ_THINGS:
                    TransferThingProperties(e.highlighted.num, e.Selected);
                    RedrawMap = 1;
                    break;
                case OBJ_LINEDEFS:
                    TransferLinedefProperties(e.highlighted.num, e.Selected);
                    RedrawMap = 1;
                    break;
                default:
                    Beep();
                    break;
                }
            }

            // [Ctrl][b] Select linedefs whose sidedefs reference non-existant sectors
            else if (is.key == 2)
            {
                bad_sector_number(&e.Selected);
                RedrawMap = 1;
            }

            // [Ctrl][p] Examine game palette (not documented)
            else if (is.key == 16)
            {
                Palette_viewer pv;
                pv.run();
                RedrawMap = 1;
            }

            // [Ctrl][r] Xref for sidedef (not documented)
            else if (is.key == 18)
            {
                xref_sidedef();
            }

            // [Ctrl][s] List secret sectors (not documented)
            else if (is.key == 19)
            {
                secret_sectors();
            }

            // [Ctrl][t] List tagged linedefs or sectors
            else if (is.key == 20)
            {
                if (e.highlighted._is_sector())
                    list_tagged_linedefs(Sectors[e.highlighted.num].tag);
                else if (e.highlighted._is_linedef())
                    list_tagged_sectors(LineDefs[e.highlighted.num].tag);
                else
                    Beep();
            }

            // [Ctrl][u] Select linedefs with unknown type (not documented)
            else if (is.key == 21)
            {
                unknown_linedef_type(&e.Selected);
                RedrawMap = 1;
            }

            // [Ctrl][v] Toggle between "snap vertex" tool and normal tool
            // (not documented)
            else if (is.key == 22)
            {
                if (e.tool == TOOL_NORMAL)
                {
                    e.tool = TOOL_SNAP_VERTEX;
                    printf("Switched to snap vertex tool."
                           " Press [Ctrl][v] to switch back to normal tool.\n");
                }
                else
                {
                    e.tool = TOOL_NORMAL;
                    printf("Switched back to normal tool.\n");
                }
            }

            // [&] Show object numbers
            else if (is.key == '&')
            {
                e.show_object_numbers = !e.show_object_numbers;
                RedrawMap = 1;
            }

            // [%] Show things sprites
            else if (is.key == '%')
            {
                e.show_things_sprites = !e.show_things_sprites;
                e.show_things_squares = !e.show_things_sprites;        // Not a typo !
                RedrawMap = 1;
            }

            /* user likes music */
            else if (is.key)
            {
                Beep();
            }
        }

        /*
         *   Step 4 -- Misc. cruft
         */

      done:

        // Auto-scrolling: scroll the map automatically
        // when the mouse pointer rests near the edge
        // of the window.
        // Scrolling is disabled when a pull-down menu
        // is visible because it would be annoying to see
        // the map scrolling while you're searching
        // through the menus.

        if (is.in_window
            && cfg.autoscroll && !is.scroll_lock && e.menubar->pulled_down() < 0)
        {
            unsigned distance;        // In pixels

#define actual_move(total,dist) \
   ((int) (((total * cfg.autoscroll_amp / 100) * ((double) (cfg.autoscroll_edge - dist) / cfg.autoscroll_edge)) / cfg.Scale))

            distance = is.y;
            // The reason for the second member of the condition
            // is that we don't want to scroll when the user is
            // simply reaching for a menu...
            if (distance <= cfg.autoscroll_edge
                && e.menubar->is_under_menubar_item(is.x) < 0)
            {
                if (MAPY(cfg.ScrCenterY) < /*MapMaxY */ 20000)
                {
                    cfg.OrigY += actual_move(cfg.ScrMaxY, distance);
                    RedrawMap = 1;
                }
            }

            distance = cfg.ScrMaxY - is.y;
            if (distance <= cfg.autoscroll_edge)
            {
                if (MAPY(cfg.ScrCenterY) > /*MapMinY */ -20000)
                {
                    cfg.OrigY -= actual_move(cfg.ScrMaxY, distance);
                    RedrawMap = 1;
                }
            }

            distance = is.x;
            if (distance <= cfg.autoscroll_edge)
            {
                if (MAPX(cfg.ScrCenterX) > /*MapMinX */ -20000)
                {
                    cfg.OrigX -= actual_move(cfg.ScrMaxX, distance);
                    RedrawMap = 1;
                }
            }

            // The reason for the second member of the condition
            // is that we don't want to scroll when the user is
            // simply reaching for the "Help" menu...
            // Note: the ordinate "3 * FONTH" is of course not
            // critical. It's just a rough approximation.
            distance = cfg.ScrMaxX - is.x;
            if (distance <= cfg.autoscroll_edge && (unsigned) is.y >= 3 * FONTH)
            {
                if (MAPX(cfg.ScrCenterX) < /*MapMaxX */ 20000)
                {
                    cfg.OrigX += actual_move(cfg.ScrMaxX, distance);
                    RedrawMap = 1;
                }
            }
        }

        /*
         *   Step 5 -- Process events that were generated
         */

      done2:

        // Process events that were generated
        if (has_event(YE_ZOOM_CHANGED) && !e.grid_step_locked)
        {
            get_event();
            edit_grid_adapt(&e);
            RedrawMap = 1;
        }

        if (RedrawMap)
            e.edisplay->need_refresh();
    }

    delete e.edisplay;
    delete e.selbox;
    delete e.menubar;
    for (size_t n = 0; n < MBM_HELP; n++)
        delete e.mb_menu[n];

    delete menu_linedef_flags;
    delete menu_thing_flags;
}


/*
 *        zoom_fit - adjust zoom factor to make level fit in window
 *
 *        Return 0 on success, non-zero on failure.
 */
static int zoom_fit(edit_t & e)
{
    // Empty level, 100% will be fine.
    if (NumVertices == 0)
        return edit_set_zoom(&e, 1.0);

    update_level_bounds();

    int MapSizeX = MapMaxX - MapMinX,
        MapSizeY = MapMaxY - MapMinY;

    double xzoom = MapSizeX ? (0.95f * cfg.ScrMaxX / (double) MapSizeX) : 1,
           yzoom = MapSizeY ? (0.9f * cfg.ScrMaxY / (double) MapSizeY) : 1;

    int r = edit_set_zoom(&e, y_min(xzoom, yzoom));
    if (r != 0)
        return 1;

    CenterMapAroundCoords((MapMinX + MapMaxX) / 2, (MapMinY + MapMaxY) / 2);
    return 0;
}