Games-Axmud

 view release on metacpan or  search on metacpan

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

            ],
            # Constant hash of names of button sets and their corresponding (short) descriptions
            constButtonDescripHash      => {
                'default'               => 'Show the default button set',    # Never actually used
                'exits'                 => 'Show exit customisation buttons',
                'painting'              => 'Show room painting buttons',
                'quick'                 => 'Show quick painting buttons',
                'background'            => 'Show background colouring buttons',
                'tracking'              => 'Show room tracking buttons',
                'misc'                  => 'Show miscellaneous buttons',
                'flags'                 => 'Show room flag filter buttons',
                'interiors'             => 'Show room interior buttons',
            },
            # Hash of names of button sets, showing which are visible (TRUE) and which are not
            #   visible (FALSE)
            buttonSetHash               => {
                'default'               => FALSE,
                'exits'                 => FALSE,
                'painting'              => FALSE,
                'quick'                 => FALSE,
                'background'            => FALSE,
                'tracking'              => FALSE,
                'misc'                  => FALSE,
                'flags'                 => FALSE,
                'interiors'             => FALSE,
            },
            # Ordered list of toolbar widgets that are visible now, with the default toolbar always
            #   first in the list
            toolbarList                 => [],
            # Corresponding hash of toolbar widgets, in the form
            #   $toolbarHash{toolbar_widget} = name_of_button_set_visible_now
            toolbarHash                 => {},
            # A list of button widgets in the original toolbar (not including the add button, the
            #   switcher button and the separator that follows them); updated every time the user
            #   clicks the switcher icon
            # NB Buttons in additional toolbars aren't stored in an IV
            toolbarButtonList           => [],
            # The 'add' button in the original toolbar
            toolbarAddButton            => undef,
            # The 'switch' button in the original toolbar
            toolbarSwitchButton         => undef,
            # The default set (the first one drawn); this IV never changes
            constToolbarDefaultSet      => 'default',
            # Whenever the original (first) toolbar is drawn, this IV records the button set used
            #   (so that the same button set appears whenever $self->redrawWidgets is called)
            toolbarOriginalSet          => 'default',
            # Hash of room flags currently in use as preferred room flags, and whose toolbar button
            #   in the 'painter' set (if visible) is currently toggled on. The hash is reset every
            #   time the toolbar is drawn or redrawn, and is used to make sure that the toggled
            #   button(s) remain toggled after the redraw
            # Hash in the form
            #   toolbarRoomFlagHash{room_flag} = undef
            toolbarRoomFlagHash         => {},
            # When a colour button in the quick painting button set it toggled, the corresponding
            #   room flag is stored here
            # When this IV is defined, clicking a room toggles the room flag in that room. If
            #   multiple rooms are selected, and one of the selected rooms was the clicked one,
            #   the room flag is toggled in all of them
            toolbarQuickPaintColour     => undef,

            # Menu bar/toolbar items which will be sensitised or desensitised, depending on the
            #   context. Hash in the form
            #       $menuToolItemHash{'item_name'} = Gtk3_widget
            # ...where:
            #   'item_name' is a descriptive scalar, e.g. 'move_up_level'
            #   'Gtk3_widget' is the Gtk3::MenuItem or toolbar widget, typically Gtk3::ToolButton or
            #       Gtk3::RadioToolButton
            # NB Entries in this hash continue to exist, after the widgets are no longer visible.
            #   Doesn't matter, because there are a limited number of 'item_name' scalars, and so
            #   a limited size to the hash (and referencing no-longer visible widgets, for example
            #   to sensitise/desensitise them, doesn't have any ill effects)
            menuToolItemHash            => {},

            # A horizontal pane, dividing the treeview on the left from everything else on the right
            hPaned                      => undef,

            # The treeview widgets (on the left)
            treeViewModel               => undef,
            treeView                    => undef,
            treeViewScroller            => undef,
            treeViewWidthPixels         => 150,     # (Default width)
            # The currently selected line of the treeview (selected by single-clicking on it)
            treeViewSelectedLine        => undef,
            # A hash of regions in the treeview, which stores which rows containing parent regions
            #   have been expanded to reveal their child regions
            # Hash in the form
            #   $treeViewRegionHash{region_name} = flag
            # ...where 'flag' is TRUE when the row is expanded, FALSE when the row is not expanded
            treeViewRegionHash          => {},
            # A hash of pointers (iters) in the treeview, so we can look up each region's cell
            treeViewPointerHash         => {},

            # Canvas widgets (on the right)
            # ->canvas and ->canvasBackground store widgets for the current region and level (or the
            #   empty background map, if no region/level are visible)
            canvas                      => undef,
            canvasBackground            => undef,
            canvasFrame                 => undef,
            canvasScroller              => undef,
            canvasHAdjustment           => undef,
            canvasVAdjustment           => undef,

            # The size of the available area inside the scrolled window, set whenever the
            #   scrolled window's size-allocate signal is emitted (this is the only way to guarantee
            #   that the correct size is available to $self->setMapPosn)
            canvasScrollerWidth         => 1,
            canvasScrollerHeight        => 1,

            # Blessed reference of the currently displayed GA::Obj::Regionmap ('undef' if no region
            #   is displayed; not necessarily the same region as the character's current location)
            currentRegionmap            => undef,
            # Blessed reference of the currently displayed GA::Obj::Parchment ('undef' if no region
            #   is displayed; not necessarily the same region as the character's current location)
            currentParchment            => undef,
            # List of the names of regions that have been the current region recently. Does not
            #   include the current region, nor any duplicates, nor more than three regions. The
            #   most recent current region is the first one in the list. The list is modified
            #   whenever $self->setCurrentRegion is called
            recentRegionList            => [],

            # Flag set to TRUE if the visible map is the empty background map (created by a call to
            #   $self->resetMap). Set to FALSE if the visible map is a region (created by a call to
            #   $self->refreshMap). Set to FALSE if neither ->resetMap nor ->refreshMap have been
            #   called yet
            emptyMapFlag                => FALSE,
            # The first call to $self->winUpdate calls $self->preparePreDraw to compile a list of

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                eastnortheast           => [2, 1, 2, 3],    # Same as E
                east                    => [2, 1, 2, 3],
                eastsoutheast           => [2, 1, 2, 3],    # Same as E
                southeast               => [2, 1, 0, 3],
                southsoutheast          => [0, 3, 2, 3],    # Same as S
                south                   => [0, 3, 2, 3],
                southsouthwest          => [0, 3, 2, 3],    # Same as S
                southwest               => [0, 1, 2, 3],
                westsouthwest           => [0, 1, 0, 3],    # Same as W
                west                    => [0, 1, 0, 3],
                westnorthwest           => [0, 1, 0, 3],    # Same as W
                northwest               => [2, 1, 0, 3],
                northnorthwest          => [0, 1, 2, 1],    # Same as N
                up                      => [0, 0],
                down                    => [0, 0],
            },
            # Anchor hashes - converts a standard primary direction into a Gtk3 anchor constant, so
            #   that exit tags can be drawn in the right position
            constGtkAnchorHash          => {
                north                   => 'GOO_CANVAS_ANCHOR_S',
                # No GooCanvas2 constant for NNE, etc, so use the same as N
                northnortheast          => 'GOO_CANVAS_ANCHOR_S',  # Same as N
                northeast               => 'GOO_CANVAS_ANCHOR_SW',
                eastnortheast           => 'GOO_CANVAS_ANCHOR_W',  # Same as E
                east                    => 'GOO_CANVAS_ANCHOR_W',
                eastsoutheast           => 'GOO_CANVAS_ANCHOR_W',  # Same as E
                southeast               => 'GOO_CANVAS_ANCHOR_NW',
                southsoutheast          => 'GOO_CANVAS_ANCHOR_N',  # Same as S
                south                   => 'GOO_CANVAS_ANCHOR_N',
                southsouthwest          => 'GOO_CANVAS_ANCHOR_N',  # Same as S
                southwest               => 'GOO_CANVAS_ANCHOR_NE',
                westsouthwest           => 'GOO_CANVAS_ANCHOR_E',  # Same as W
                west                    => 'GOO_CANVAS_ANCHOR_E',
                westnorthwest           => 'GOO_CANVAS_ANCHOR_E',  # Same as w
                northwest               => 'GOO_CANVAS_ANCHOR_SE',
                northnorthwest          => 'GOO_CANVAS_ANCHOR_S',  # Same as N
            },

            # Magnfication list. A list of standard magnification factors used for zooming in or out
            #   from the map
            # Each GA::Obj::Regionmap object has its own ->magnification IV, so zooming on one
            #   region doesn't affect the magnification of others
            # When the user zooms in or out, ->magnification is set to one of the values in this
            #   list, and various IVs in GA::Obj::Regionmap (such as ->blockWidthPixels and
            #   ->roomHeightPixels) are changed. When the map is redrawn, everything in it is bigger
            #   (or smaller)
            constMagnifyList            => [
                0.01, 0.02, 0.04, 0.06, 0.08, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
                1,
                1.1, 1.2, 1.35, 1.5, 2, 3, 5, 7, 10,
            ],
            # A subset of these magnifications, used as menu items
            constShortMagnifyList       => [
                0.5, 0.8, 1, 1.2, 1.5, 1.75, 2
            ],
            # When some menu items are selected (e.g. View > Room filters > Release markers filter),
            #   a call is made to this session's GA::Obj::WorldModel, which in turn calls every
            #   Automapper window using the model, in order to update its menu. When this happens,
            #   the following flag is set to TRUE, so that updating the menu item doesn't cause
            #   further calls to GA::Obj::WorldModel
            ignoreMenuUpdateFlag        => FALSE,

            # IVs used during a drag operation
            # Flag set to TRUE during drag mode (set from the menu or the toolbar). Normally, it's
            #   necessary to hold down the Alt-Gr key to drag canvas objects; when drag mode is on,
            #   clicks on canvas objects are treated as the start of a drag, rather than a
            #   select/unselect operation)
            # NB During a drag operation initiated with the Alt-Gr key, ->dragModeFlag's value
            #   doesn't change
            dragModeFlag                => FALSE,
            # Flag set to TRUE when a dragging operation starts
            dragFlag                    => FALSE,
            # Flag set to TRUE when $self->continueDrag is called, and set back to FALSE at the
            #   end of that call. ->continueDrag does nothing if a previous call to the function
            #   hasn't been completed (happens a lot)
            dragContinueFlag            => FALSE,
            # The canvas object that was underneath the mouse cursor when the drag operation began
            #   (the object that was grabbed, when using Alt-Gr)
            dragCanvasObj               => undef,
            # A list of all canvas objects that are being dragged together. $self->dragCanvasObj is
            #   always the first item in the list
            # If $self->dragCanvasObj is a room, all selected rooms/labels in the same region are
            #   dragged together
            # If $self->dragCanvasObj is a label, both the label and its box (if drawn) are dragged
            #   together
            dragCanvasObjList           => [],
            # The GA::ModelObj::Room / GA::Obj::Exit / GA::Obj::MapLabel being dragged,
            #   corresponding to $self->dragCanvasObj
            dragModelObj                => undef,
            # The type of object being dragged - 'room', 'room_tag', 'room_guild', 'exit',
            #   'exit_tag' or 'label'
            dragModelObjType            => undef,
            # The canvas object's initial coordinates on the canvas
            dragInitXPos                => undef,
            dragInitYPos                => undef,
            # The canvas object's current coordinates on the canvas
            dragCurrentXPos             => undef,
            dragCurrentYPos             => undef,
            # When dragging a room(s), the fake room(s) drawn at the original location (so that the
            #   exits don't look messy)
            dragFakeRoomList            => [],
            # When dragging an exit bend, the bend's index in the exit's list of bends (the bend
            #   closest to the start of the exit has the index 0)
            dragBendNum                 => undef,
            # When dragging an exit bend, the initial position of the bend, relative to the start of
            #   the bending section of the exit
            dragBendInitXPos            => undef,
            dragBendInitYPos            => undef,
            # The corresponding IVs for the twin exit, when dragging an exit bend
            dragBendTwinNum             => undef,
            dragBendTwinInitXPos        => undef,
            dragBendTwinInitYPos        => undef,
            # When dragging an exit bend, the exit drawing mode (corresponds to
            #   GA::Obj::WorldModel->drawExitMode)
            dragExitDrawMode            => undef,
            # When dragging an exit bend, the draw ornaments flag (corresponds to
            #   GA:Obj::WorldModel->drawOrnamentsFlag
            dragExitOrnamentsFlag       => undef,

            # IVs used during a selection box operation
            # Flag set to TRUE when a selection box operation starts, but before the box has

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN


        # Destroy the Gtk3::Window
        eval { $self->winBox->destroy(); };
        if ($@) {

            # Window can't be destroyed
            return undef;

        } else {

            $self->ivUndef('winWidget');
            $self->ivUndef('winBox');
        }

        # Inform the ->owner, if there is one
        if ($self->owner) {

            $self->owner->del_winObj($self);
        }

        # This type of window is unique to its GA::Session (only one can be open at any time, per
        #   session); inform the session it has closed
        $self->session->set_mapWin();

        return 1;
    }

#   sub winShowAll {}           # Inherited from GA::Win::Generic

    sub drawWidgets {

        # Called by $self->winSetup
        # Sets up the Gtk3::Window by drawing its widgets
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 on success

        my ($self, $check) = @_;

        # Local variables
        my ($menuBar, $hPaned, $treeViewScroller, $canvasFrame);

        # Check for improper arguments
        if (defined $check) {

             return $axmud::CLIENT->writeImproper($self->_objClass . '->drawWidgets', @_);
        }

        # Create a packing box
        my $packingBox = Gtk3::VBox->new(FALSE, 0);
        $self->winBox->add($packingBox);
        $packingBox->set_border_width(0);
        # Update IVs immediately
        $self->ivPoke('packingBox', $packingBox);

        # Create a menu (if allowed)
        if ($self->worldModelObj->showMenuBarFlag) {

            $menuBar = $self->enableMenu();
            if ($menuBar) {

                # Pack the widget
                $packingBox->pack_start($menuBar, FALSE, FALSE, 0);
            }
        }

        # Create toolbar(s) at the top of the window (if allowed)
        if ($self->worldModelObj->showToolbarFlag) {

            # Reset toolbar IVs to their default state; the subsequent call to $self->enableToolbar
            #   imports the list of button sets from the world model, and updates these IVs
            #   accordinly
            $self->resetToolbarIVs();

            foreach my $toolbar ($self->enableToolbar()) {

                # Pack the widget
                $packingBox->pack_start($toolbar, FALSE, FALSE, 0);
            }
        }

        # Create a horizontal pane to divide everything under the menu into two, with the treeview
        #   on the left, and everything else on the right (only if both the treeview and the canvas
        #   are shown)
        if ($self->worldModelObj->showTreeViewFlag && $self->worldModelObj->showCanvasFlag) {

            $hPaned = Gtk3::HPaned->new();
            if ($hPaned) {

                # Set the width of the space about to be filled with the treeview
                $hPaned->set_position($self->treeViewWidthPixels);

                # Pack the widget
                $packingBox->pack_start($hPaned, TRUE, TRUE, 0);
                $self->ivPoke('hPaned', $hPaned);
            }
        }

        # Create a treeview (if allowed)
        if ($self->worldModelObj->showTreeViewFlag) {

            $treeViewScroller = $self->enableTreeView();
            if ($treeViewScroller) {

                # Pack the widget
                if ($hPaned) {

                    # Add the treeview's scroller to the left pane
                    $hPaned->add1($treeViewScroller);

                } else {

                    # Pack the treeview directly into the packing box
                    $packingBox->pack_start($treeViewScroller, TRUE, TRUE, 0);
                }
            }
        }

        # Create a canvas (if allowed)
        if ($self->worldModelObj->showCanvasFlag) {

            $canvasFrame = $self->enableCanvas();
            if ($canvasFrame) {

                # Pack the widget
                if ($hPaned) {

                    # Add the frame to the right pane
                    $hPaned->add2($canvasFrame);

                } else {

                    # Pack the frame directly into the packing box
                    $packingBox->pack_start($canvasFrame, TRUE, TRUE, 0);
                }
            }
        }

        return 1;
    }

    sub redrawWidgets {

        # Can be called by any function
        # Redraws some or all of the menu bar, toolbar(s), treeview and canvas
        # The widgets redrawn are specified by the calling function, but are not redrawn if the
        #   right flags aren't set (e.g. the menu bar isn't redrawn if
        #   GA::Obj::WorldModel->showMenuBarFlag isn't set)
        #
        # Expected arguments
        #   @widgetList - A list of widget names. One or all of the following strings, in any order:
        #                   'menu_bar', 'toolbar', 'treeview', 'canvas'
        #
        # Return values
        #   'undef' on improper arguments or if any of the widgets in @widgetList are unrecognised
        #   1 otherwise

        my ($self, @widgetList) = @_;

        # Local variables
        my (
            $menuBar, $hPaned, $treeViewScroller, $canvasFrame,
            @toolbarList,
            %widgetHash,
        );

        # Check for improper arguments
        if (! @widgetList) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->redrawWidgets', @_);
        }

        # Check that the strings in @widgetList are valid, and add each string into a hash so that
        #   no widget is drawn more than once
        # Initialise the hash of allowed widgets
        %widgetHash = (
            'menu_bar'  => FALSE,
            'toolbar'   => FALSE,
            'treeview'  => FALSE,
            'canvas'    => FALSE,
        );

        # Check everything in @widgetList
        foreach my $name (@widgetList) {

            if (! exists $widgetHash{$name}) {

                return $self->session->writeError(
                    'Unrecognised widget \'' . $name . '\'',
                    $self->_objClass . '->redrawWidgets',
                );

            } else {

                # If the same string appears more than once in @widgetList, we only draw the widget
                #   once
                $widgetHash{$name} = TRUE;
            }
        }

        # Remove the old widgets from the vertical packing box
        if ($self->menuBar) {

            $axmud::CLIENT->desktopObj->removeWidget($self->packingBox, $self->menuBar);
        }

        foreach my $toolbar ($self->toolbarList) {

            $axmud::CLIENT->desktopObj->removeWidget($self->packingBox, $toolbar);
        }

        if ($self->hPaned) {

            foreach my $child ($self->hPaned->get_children()) {

                $self->hPaned->remove($child);
            }

            $axmud::CLIENT->desktopObj->removeWidget($self->packingBox, $self->hPaned);

        } else {

            if ($self->treeViewScroller) {

                $axmud::CLIENT->desktopObj->removeWidget(
                    $self->packingBox,
                    $self->treeViewScroller,
                );
            }

            if ($self->canvasFrame) {

                $axmud::CLIENT->desktopObj->removeWidget($self->packingBox, $self->canvasFrame);
            }
        }

        # Redraw the menu bar, if specified (and if allowed)
        if ($self->worldModelObj->showMenuBarFlag) {

            if ($widgetHash{'menu_bar'}) {

                $self->resetMenuBarIVs();

                my $menuBar = $self->enableMenu();
                if ($menuBar) {

                    # Pack the new widget
                    $self->packingBox->pack_start($menuBar,FALSE,FALSE,0);

                } else {

                    # After the error, stop trying to draw menu bars
                    $self->worldModelObj->set_showMenuBarFlag(FALSE);
                }

            # Otherwise, repack the old menu bar
            } elsif ($self->menuBar) {

                $self->packingBox->pack_start($self->menuBar,FALSE,FALSE,0);
            }
        }

        # Redraw the toolbar(s), if specified (and if allowed)
        if ($self->worldModelObj->showToolbarFlag) {

            if ($widgetHash{'toolbar'}) {

                # Reset toolbar IVs to their default state; the subsequent call to
                #   $self->enableToolbar imports the list of button sets from the world model, and
                #   updates these IVs accordinly
                $self->resetToolbarIVs();

                @toolbarList = $self->enableToolbar();
                if (@toolbarList) {

                    foreach my $toolbar (@toolbarList) {

                        # Pack the new widget
                        $self->packingBox->pack_start($toolbar, FALSE, FALSE, 0);
                    }

                } else {

                    # After the error, stop trying to draw toolbars
                    $self->worldModelObj->set_showToolbarFlag(FALSE);
                }

            # Otherwise, repack the old toolbar(s)
            } else {

                foreach my $toolbar ($self->toolbarList) {

                    $self->packingBox->pack_start($toolbar, FALSE, FALSE, 0);
                }
            }

        } else {

            # When the toolbars are next drawn, make sure the default button set is visible in the
            #   original (first) toolbar
            $self->ivPoke('toolbarOriginalSet', $self->constToolbarDefaultSet);
        }

        # Create a new horizontal pane (only if both the treeview and the canvas are allowed)
        if ($self->worldModelObj->showTreeViewFlag && $self->worldModelObj->showCanvasFlag) {

            $hPaned = Gtk3::HPaned->new();
            if ($hPaned) {

                # Set the width of the space about to be filled with the treeview
                $hPaned->set_position($self->treeViewWidthPixels);

                # Pack the widget

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

            } elsif ($self->treeViewScroller) {

                if ($hPaned) {

                    # Add the treeview's scroller to the left-hand pane
                    $hPaned->add1($self->treeViewScroller);

                } else {

                    # Pack the treeview directly into the packing box
                    $self->packingBox->pack_start($self->treeViewScroller, TRUE, TRUE, 0);
                }
            }
        }

        # Redraw the canvas, if specified (and if allowed)
        if ($self->worldModelObj->showCanvasFlag) {

            if ($widgetHash{'canvas'}) {

                $self->resetCanvasIVs();

                $canvasFrame = $self->enableCanvas();
                if ($canvasFrame) {

                    # Pack the new widget
                    if ($hPaned) {

                        # Add the frame to the right pane
                        $hPaned->add2($canvasFrame);

                    } else {

                        # Pack the frame directly into the packing box
                        $self->packingBox->pack_start($canvasFrame, TRUE, TRUE, 0);
                    }

                } else {

                    # After the error, stop trying to draw canvases
                    $self->worldModelObj->set_showCanvasFlag(FALSE);
                }

            # Otherwise, repack the old canvas
            } elsif ($self->canvasFrame) {

                if ($hPaned) {

                    # Add the frame to the right-hand pane
                    $hPaned->add2($self->canvasFrame);

                } else {

                    # Pack the frame directly into the packing box
                    $self->packingBox->pack_start($self->canvasFrame, TRUE, TRUE, 0);
                }
            }
        }

        # Now, for each widget that is no longer drawn, set default IVs
        if (! $self->worldModelObj->showMenuBarFlag) {

            $self->resetMenuBarIVs();
        }

        if (! $self->worldModelObj->showToolbarFlag) {

            $self->resetToolbarIVs();
        }

        if (! $self->worldModelObj->showTreeViewFlag || ! $self->worldModelObj->showCanvasFlag) {

            $self->ivUndef('hPaned');
        }

        if (! $self->worldModelObj->showTreeViewFlag) {

            $self->resetTreeViewIVs();
        }

        if (! $self->worldModelObj->showCanvasFlag) {

            $self->resetCanvasIVs();
        }

        # Repack complete
        $self->winShowAll($self->_objClass . '->redrawWidgets');
        $axmud::CLIENT->desktopObj->updateWidgets($self->_objClass . '->redrawWidgets');

        return 1;
    }

    # Standard 'map' window object functions

    sub winReset {

        # Called by GA::Obj::Map->openWin to reset an existing Automapper window
        #
        # Expected arguments
        #   $mapObj     - The calling GA::Obj::Map object
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $mapObj, $check) = @_;

        # Check for improper arguments
        if (! defined $mapObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->winReset', @_);
        }

        # Set new Perl object component IVs
        $self->ivPoke('mapObj', $mapObj);
        $self->ivPoke('worldModelObj', $self->session->worldModelObj);

        # Reset the current region
        $self->ivUndef('currentRegionmap');
        $self->ivUndef('currentParchment');
        $self->ivEmpty('recentRegionList');

        # Reset parchment objects (which destroys all canvas widgets except the empty one created

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN


        $self->winBox->signal_connect('configure-event' => sub {

            my ($widget, $event) = @_;

            # Let the GA::Client store the most recent size and position for a window of this
            #   ->winName, if it needs to
            if ($self->winWidget) {

                $axmud::CLIENT->add_storeGridPosn(
                    $self,
                    $self->winWidget->get_position(),
                    $self->winWidget->get_size(),
                );
            }

            # Without returning 'undef', the window's strip/table objects aren't resized along with
            #   the window
            return undef;
        });

        return 1;
    }

    sub setFocusOutEvent {

        # Called by $self->winSetup
        # Set up a ->signal_connect to watch out for the 'map' window losing the focus
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

             return $axmud::CLIENT->writeImproper($self->_objClass . '->setFocusInEvent', @_);
        }

        $self->winBox->signal_connect('focus-out-event' => sub {

            my ($widget, $event) = @_;

            # If the tooltips are visible, hide them
            if ($event->type eq 'focus-change' && $self->canvasTooltipFlag) {

               $self->hideTooltips();
            }
        });

        return 1;
    }

    # Other functions

    sub resetMenuBarIVs {

        # Called by $self->redrawWidgets at certain points, to reset the IVs storing details about
        #   the menu bar back to their defaults
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->resetMenuBarIVs', @_);
        }

        $self->ivUndef('menuBar');
        $self->ivEmpty('menuToolItemHash');

        return 1;
    }

    sub resetToolbarIVs {

        # Called by $self->drawWidgets and $self->redrawWidget to reset the IVs storing details
        #   about toolbars back to their defaults
        # (If $self->enableToolbar is then called, it's that function which imports a list of
        #   button sets from the world model and updates these IVs accordinly)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->resetToolbarIVs', @_);
        }

        foreach my $key ($self->ivKeys('buttonSetHash')) {

            $self->ivAdd('buttonSetHash', $key, FALSE);
        }

        $self->ivEmpty('toolbarList');
        $self->ivEmpty('toolbarHash');

        return 1;
    }

    sub resetTreeViewIVs {

        # Called by $self->redrawWidgets at certain points, to reset the IVs storing details about
        #   the treeview back to their defaults
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->resetTreeViewIVs', @_);
        }

        $self->ivUndef('treeViewModel');
        $self->ivUndef('treeView');
        $self->ivUndef('treeViewScroller');
        $self->ivUndef('treeViewSelectedLine');
        $self->ivEmpty('treeViewRegionHash');
        $self->ivEmpty('treeViewPointerHash');

        return 1;
    }

    sub resetCanvasIVs {

        # Called by $self->redrawWidgets at certain points, to reset the IVs storing details about
        #   the canvas back to their defaults
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->resetCanvasIVs', @_);
        }

        $self->ivUndef('canvas');
        $self->ivUndef('canvasBackground');
        # (For some reason, commenting out these lines decreases the draw time, during a call to
        #   $self->redrawWidgets, by about 40%. The IVs receive their correct values anyway when
        #   ->enableCanvas is called)
#        $self->ivUndef('canvasFrame');
#        $self->ivUndef('canvasScroller');
        $self->ivUndef('canvasHAdjustment');
        $self->ivUndef('canvasVAdjustment');

        $self->ivUndef('canvasTooltipObj');
        $self->ivUndef('canvasTooltipObjType');
        $self->ivUndef('canvasTooltipFlag');

        return 1;
    }

    # Menu widget methods

    sub enableMenu {

        # Called by $self->drawWidgets
        # Sets up the Automapper window's Gtk3::MenuBar widget
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::MenuBar created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableMenu', @_);
        }

        # Create the menu bar
        my $menuBar = Gtk3::MenuBar->new();
        if (! $menuBar) {

            return undef;
        }

        # 'File' column
        my $column_file = $self->enableFileColumn();
        my $item_file = Gtk3::MenuItem->new('_File');
        $item_file->set_submenu($column_file);
        $menuBar->append($item_file);

        # 'Edit' column
        my $column_edit = $self->enableEditColumn();
        my $item_edit = Gtk3::MenuItem->new('_Edit');
        $item_edit->set_submenu($column_edit);
        $menuBar->append($item_edit);

        # 'View' column
        my $column_view = $self->enableViewColumn();
        my $item_view = Gtk3::MenuItem->new('_View');
        $item_view->set_submenu($column_view);
        $menuBar->append($item_view);

        # 'Mode' column
        my $column_mode = $self->enableModeColumn();
        my $item_mode = Gtk3::MenuItem->new('_Mode');
        $item_mode->set_submenu($column_mode);
        $menuBar->append($item_mode);

        # 'Regions' column
        my $column_regions = $self->enableRegionsColumn();
        my $item_regions = Gtk3::MenuItem->new('_Regions');
        $item_regions->set_submenu($column_regions);
        $menuBar->append($item_regions);

        # 'Rooms' column
        my $column_rooms = $self->enableRoomsColumn();
        my $item_rooms = Gtk3::MenuItem->new('R_ooms');
        $item_rooms->set_submenu($column_rooms);
        $menuBar->append($item_rooms);

        # 'Exits' column
        my $column_exits = $self->enableExitsColumn();
        my $item_exits = Gtk3::MenuItem->new('E_xits');
        $item_exits->set_submenu($column_exits);
        $menuBar->append($item_exits);

        # 'Labels' column
        my $column_labels = $self->enableLabelsColumn();
        my $item_labels = Gtk3::MenuItem->new('_Labels');
        $item_labels->set_submenu($column_labels);
        $menuBar->append($item_labels);

        # Store the widget
        $self->ivPoke('menuBar', $menuBar);

        # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
        $self->restrictWidgets();

        # Setup complete
        return $menuBar;
    }

    sub enableFileColumn {

        # Called by $self->enableMenu
        # Sets up the 'File' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableFileColumn', @_);
        }

        # Set up column
        my $column_file = Gtk3::Menu->new();
        if (! $column_file) {

            return undef;
        }

        my $item_loadModel = Gtk3::MenuItem->new('_Load world model');
        $item_loadModel->signal_connect('activate' => sub {

            # $self->winReset will be called by $self->set_worldModelObj when the ';load' command
            #   has finished its work
            # NB Force pseudo command mode 'win_error' in this menu column (success system messages
            #   in the 'main' window; errors/improper arguments messages shown in a 'dialogue'
            #   window)
            $self->session->pseudoCmd('load -m', 'win_error');
        });
        $column_file->append($item_loadModel);

        my $item_loadAll = Gtk3::ImageMenuItem->new('L_oad all files');
        $item_loadAll->signal_connect('activate' => sub {

            # The ';load' command will  $self->winReset when finished

            $self->session->pseudoCmd('load', 'win_error');
        });
        my $img_loadAll = Gtk3::Image->new_from_stock('gtk-open', 'menu');
        $item_loadAll->set_image($img_loadAll);
        $column_file->append($item_loadAll);

        $column_file->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_saveModel = Gtk3::MenuItem->new('_Save world model');
        $item_saveModel->signal_connect('activate' => sub {

            # Do a forced save. The ';save' command sets $self->freeClickMode back to 'default'
            $self->session->pseudoCmd('save -m -f', 'win_error');
        });
        $column_file->append($item_saveModel);

        my $item_saveAll = Gtk3::ImageMenuItem->new('S_ave all files');
        $item_saveAll->signal_connect('activate' => sub {

            # Do a forced save. The ';save' command sets $self->freeClickMode back to 'default'
            $self->session->pseudoCmd('save -f', 'win_error');
        });
        my $img_saveAll = Gtk3::Image->new_from_stock('gtk-save', 'menu');
        $item_saveAll->set_image($img_saveAll);
        $column_file->append($item_saveAll);

        $column_file->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_importModel = Gtk3::MenuItem->new('_Import/load world model...');
        $item_importModel->signal_connect('activate' => sub {

            $self->importModelCallback();
        });
        $column_file->append($item_importModel);

        my $item_exportModel = Gtk3::MenuItem->new('Save/_export world model...');
        $item_exportModel->signal_connect('activate' => sub {

            $self->exportModelCallback();
        });
        $column_file->append($item_exportModel);

        $column_file->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_mergeModel = Gtk3::MenuItem->new('_Merge world models...');
        $item_mergeModel->signal_connect('activate' => sub {

            $self->session->pseudoCmd('mergemodel')
        });
        $column_file->append($item_mergeModel);

        $column_file->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_closeWindow = Gtk3::ImageMenuItem->new('_Close window');
        $item_closeWindow->signal_connect('activate' => sub {

            $self->winDestroy();
        });
        my $img_closeWindow = Gtk3::Image->new_from_stock('gtk-quit', 'menu');
        $item_closeWindow->set_image($img_closeWindow);
        $column_file->append($item_closeWindow);

        # Setup complete
        return $column_file;
    }

    sub enableEditColumn {

        # Called by $self->enableMenu
        # Sets up the 'Edit' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Local variables
        my $winObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableEditColumn', @_);
        }

        # Set up column
        my $column_edit = Gtk3::Menu->new();
        if (! $column_edit) {

            return undef;
        }

            # 'Select' submenu
            my $subMenu_select = Gtk3::Menu->new();

                # 'Select rooms' sub-submenu
                my $subSubMenu_selectRooms = Gtk3::Menu->new();

                my $item_selectNoTitle = Gtk3::MenuItem->new('Rooms with no _titles');
                $item_selectNoTitle->signal_connect('activate' => sub {

                    $self->selectRoomCallback('no_title');
                });
                $subSubMenu_selectRooms->append($item_selectNoTitle);

                my $item_selectNoDescrip = Gtk3::MenuItem->new('Rooms with no _descriptions');
                $item_selectNoDescrip->signal_connect('activate' => sub {

                    $self->selectRoomCallback('no_descrip');
                });
                $subSubMenu_selectRooms->append($item_selectNoDescrip);

                my $item_selectNoTitleDescrip = Gtk3::MenuItem->new('Rooms with _neither');
                $item_selectNoTitleDescrip->signal_connect('activate' => sub {

                    $self->selectRoomCallback('no_title_descrip');
                });
                $subSubMenu_selectRooms->append($item_selectNoTitleDescrip);

                my $item_selectTitleDescrip = Gtk3::MenuItem->new('Rooms with _both');
                $item_selectTitleDescrip->signal_connect('activate' => sub {

                    $self->selectRoomCallback('title_descrip');
                });
                $subSubMenu_selectRooms->append($item_selectTitleDescrip);

                $subSubMenu_selectRooms->append(Gtk3::SeparatorMenuItem->new());   # Separator

                my $item_selectNoVisitChar = Gtk3::MenuItem->new('Rooms not visited by _character');
                $item_selectNoVisitChar->signal_connect('activate' => sub {

                    $self->selectRoomCallback('no_visit_char');
                });
                $subSubMenu_selectRooms->append($item_selectNoVisitChar);

                my $item_selectNoVisitAllChar = Gtk3::MenuItem->new('Rooms not visited by _anyone');
                $item_selectNoVisitAllChar->signal_connect('activate' => sub {

                    $self->selectRoomCallback('no_visit_all');
                });
                $subSubMenu_selectRooms->append($item_selectNoVisitAllChar);

                my $item_selectVisitChar = Gtk3::MenuItem->new('Rooms visited by c_haracter');
                $item_selectVisitChar->signal_connect('activate' => sub {

                    $self->selectRoomCallback('visit_char');
                });
                $subSubMenu_selectRooms->append($item_selectVisitChar);

                my $item_selectVisitAllChar = Gtk3::MenuItem->new('Rooms visited by an_yone');
                $item_selectVisitAllChar->signal_connect('activate' => sub {

                    $self->selectRoomCallback('visit_all');
                });
                $subSubMenu_selectRooms->append($item_selectVisitAllChar);

                $subSubMenu_selectRooms->append(Gtk3::SeparatorMenuItem->new());   # Separator

                my $item_selectCheckable = Gtk3::MenuItem->new('Rooms with checkable d_irections');
                $item_selectCheckable->signal_connect('activate' => sub {

                    $self->selectRoomCallback('checkable');
                });
                $subSubMenu_selectRooms->append($item_selectCheckable);

            my $item_selectRooms = Gtk3::MenuItem->new('Select _rooms');
            $item_selectRooms->set_submenu($subSubMenu_selectRooms);
            $subMenu_select->append($item_selectRooms);

                # 'Select exits' sub-submenu
                my $subSubMenu_selectExits = Gtk3::Menu->new();

                my $item_selectInRooms = Gtk3::MenuItem->new('Exits in selected _rooms');
                $item_selectInRooms->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('in_rooms');
                });
                $subSubMenu_selectExits->append($item_selectInRooms);

                $subSubMenu_selectExits->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_selectUnallocated = Gtk3::MenuItem->new('_Unallocated exits');
                $item_selectUnallocated->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('unallocated');
                });
                $subSubMenu_selectExits->append($item_selectUnallocated);

                my $item_selectUnallocatable = Gtk3::MenuItem->new('U_nallocatable exits');
                $item_selectUnallocatable->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('unallocatable');
                });
                $subSubMenu_selectExits->append($item_selectUnallocatable);

                my $item_selectUncertain = Gtk3::MenuItem->new('Un_certain exits');
                $item_selectUncertain->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('uncertain');
                });
                $subSubMenu_selectExits->append($item_selectUncertain);

                my $item_selectIncomplete = Gtk3::MenuItem->new('_Incomplete exits');
                $item_selectIncomplete->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('incomplete');
                });
                $subSubMenu_selectExits->append($item_selectIncomplete);

                my $item_selectAllAbove = Gtk3::MenuItem->new('_All of the above');
                $item_selectAllAbove->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('all_above');
                });
                $subSubMenu_selectExits->append($item_selectAllAbove);

                $subSubMenu_selectExits->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_selectImpassable = Gtk3::MenuItem->new('I_mpassable exits');
                $item_selectImpassable->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('impass');
                });
                $subSubMenu_selectExits->append($item_selectImpassable);

                my $item_selectMystery = Gtk3::MenuItem->new('M_ystery exits');
                $item_selectMystery->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('mystery');
                });
                $subSubMenu_selectExits->append($item_selectMystery);

                $subSubMenu_selectExits->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_selectNonSuper = Gtk3::MenuItem->new('R_egion exits');
                $item_selectNonSuper->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('region');
                });
                $subSubMenu_selectExits->append($item_selectNonSuper);

                my $item_selectSuper = Gtk3::MenuItem->new('_Super-region exits');
                $item_selectSuper->signal_connect('activate' => sub {

                    $self->selectExitTypeCallback('super');
                });
                $subSubMenu_selectExits->append($item_selectSuper);

            my $item_selectExits = Gtk3::MenuItem->new('Select _exits');
            $item_selectExits->set_submenu($subSubMenu_selectExits);
            $subMenu_select->append($item_selectExits);

            $subMenu_select->append(Gtk3::SeparatorMenuItem->new());   # Separator

                # 'Select in region' sub-submenu
                my $subSubMenu_selectRegion = Gtk3::Menu->new();

                my $item_selectRegionRoom = Gtk3::MenuItem->new('Every _room');
                $item_selectRegionRoom->signal_connect('activate' => sub {

                    $self->selectInRegionCallback('room');
                });
                $subSubMenu_selectRegion->append($item_selectRegionRoom);

                my $item_selectRegionExit = Gtk3::MenuItem->new('Every _exit');
                $item_selectRegionExit->signal_connect('activate' => sub {

                    $self->selectInRegionCallback('exit');
                });
                $subSubMenu_selectRegion->append($item_selectRegionExit);

                my $item_selectRegionRoomTag = Gtk3::MenuItem->new('Every room _tag');
                $item_selectRegionRoomTag->signal_connect('activate' => sub {

                    $self->selectInRegionCallback('room_tag');
                });
                $subSubMenu_selectRegion->append($item_selectRegionRoomTag);

                my $item_selectRegionRoomGuild = Gtk3::MenuItem->new('Every room _guild');
                $item_selectRegionRoomGuild->signal_connect('activate' => sub {

                    $self->selectInRegionCallback('room_guild');
                });
                $subSubMenu_selectRegion->append($item_selectRegionRoomGuild);

                my $item_selectRegionLabel = Gtk3::MenuItem->new('Every _label');
                $item_selectRegionLabel->signal_connect('activate' => sub {

                    $self->selectInRegionCallback('label');
                });
                $subSubMenu_selectRegion->append($item_selectRegionLabel);

                $subSubMenu_selectRegion->append(Gtk3::SeparatorMenuItem->new());   # Separator

                my $item_selectRegionAbove = Gtk3::MenuItem->new('_All of the above');
                $item_selectRegionAbove->signal_connect('activate' => sub {

                    $self->selectInRegionCallback();
                });
                $subSubMenu_selectRegion->append($item_selectRegionAbove);

            my $item_selectRegion = Gtk3::MenuItem->new('Select in re_gion');
            $item_selectRegion->set_submenu($subSubMenu_selectRegion);
            $subMenu_select->append($item_selectRegion);

                # 'Select in map' sub-submenu
                my $subSubMenu_selectMap = Gtk3::Menu->new();

                my $item_selectMapRoom = Gtk3::MenuItem->new('Every _room');
                $item_selectMapRoom->signal_connect('activate' => sub {

                    $self->selectInMapCallback('room');
                });
                $subSubMenu_selectMap->append($item_selectMapRoom);

                my $item_selectMapExit = Gtk3::MenuItem->new('Every _exit');
                $item_selectMapExit->signal_connect('activate' => sub {

                    $self->selectInMapCallback('exit');
                });
                $subSubMenu_selectMap->append($item_selectMapExit);

                my $item_selectMapRoomTag = Gtk3::MenuItem->new('Every room _tag');
                $item_selectMapRoomTag->signal_connect('activate' => sub {

                    $self->selectInMapCallback('room_tag');
                });
                $subSubMenu_selectMap->append($item_selectMapRoomTag);

                my $item_selectMapRoomGuild = Gtk3::MenuItem->new('Every room _guild');
                $item_selectMapRoomGuild->signal_connect('activate' => sub {

                    $self->selectInMapCallback('room_guild');
                });
                $subSubMenu_selectMap->append($item_selectMapRoomGuild);

                my $item_selectMapLabel = Gtk3::MenuItem->new('Every _label');
                $item_selectMapLabel->signal_connect('activate' => sub {

                    $self->selectInMapCallback('label');
                });
                $subSubMenu_selectMap->append($item_selectMapLabel);

                $subSubMenu_selectMap->append(Gtk3::SeparatorMenuItem->new());   # Separator

                my $item_selectMapAbove = Gtk3::MenuItem->new('_All of the above');
                $item_selectMapAbove->signal_connect('activate' => sub {

                    $self->selectInMapCallback();
                });
                $subSubMenu_selectMap->append($item_selectMapAbove);

            my $item_selectMap = Gtk3::MenuItem->new('Select in _map');
            $item_selectMap->set_submenu($subSubMenu_selectMap);
            $subMenu_select->append($item_selectMap);

        my $item_select = Gtk3::MenuItem->new('_Select');
        $item_select->set_submenu($subMenu_select);
        $column_edit->append($item_select);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'select', $item_select);

            # 'Selected items' submenu
            my $subMenu_selectedObjs = Gtk3::Menu->new();

            my $item_identifyRoom = Gtk3::MenuItem->new('Identify _room(s)');
            $item_identifyRoom->signal_connect('activate' => sub {

                $self->identifyRoomsCallback();
            });
            $subMenu_selectedObjs->append($item_identifyRoom);
            # (Requires $self->currentRegionmap and EITHER $self->selectedRoom or
            #   $self->selectedRoomHash or $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'identify_room', $item_identifyRoom);

            my $item_identifyExit = Gtk3::MenuItem->new('Identify _exit(s)');
            $item_identifyExit->signal_connect('activate' => sub {

                $self->identifyExitsCallback();
            });
            $subMenu_selectedObjs->append($item_identifyExit);
            # (Requires $self->currentRegionmap & either $self->selectedExit or
            #   $self->selectedExitHash)
            $self->ivAdd('menuToolItemHash', 'identify_exit', $item_identifyExit);

        my $item_selectedObjs = Gtk3::MenuItem->new('_Identify selected items');
        $item_selectedObjs->set_submenu($subMenu_selectedObjs);
        $column_edit->append($item_selectedObjs);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'selected_objs', $item_selectedObjs);

        my $item_unselectAll = Gtk3::MenuItem->new('_Unselect all');
        $item_unselectAll->signal_connect('activate' => sub {

            $self->setSelectedObj();
        });
        $column_edit->append($item_unselectAll);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'unselect_all', $item_unselectAll);

        $column_edit->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Search' submenu
            my $subMenu_search = Gtk3::Menu->new();

            my $item_searchModel = Gtk3::MenuItem->new('Search world _model...');
            $item_searchModel->signal_connect('activate' => sub {

                # Open a 'pref' window to conduct the search
                $self->createFreeWin(
                    'Games::Axmud::PrefWin::Search',
                    $self,
                    $self->session,
                    'World model search',
                );
            });
            $subMenu_search->append($item_searchModel);

            $subMenu_search->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_findRoom = Gtk3::MenuItem->new('Find _room...');
            $item_findRoom->signal_connect('activate' => sub {

                $self->findRoomCallback();
            });
            $subMenu_search->append($item_findRoom);

            my $item_findExit = Gtk3::MenuItem->new('Find _exit...');
            $item_findExit->signal_connect('activate' => sub {

                $self->findExitCallback();
            });
            $subMenu_search->append($item_findExit);

        my $item_search = Gtk3::ImageMenuItem->new('S_earch');
        my $img_search = Gtk3::Image->new_from_stock('gtk-find', 'menu');
        $item_search->set_image($img_search);
        $item_search->set_submenu($subMenu_search);
        $column_edit->append($item_search);

            # 'Generate reports' submenu
            my $subMenu_reports = Gtk3::Menu->new();

            my $item_showSummary = Gtk3::MenuItem->new('_Show general report');
            $item_showSummary->signal_connect('activate' => sub {

                # (Don't use $self->pseudoCmdMode - we want to see the footer messages)
                $self->session->pseudoCmd('modelreport', 'show_all');
            });
            $subMenu_reports->append($item_showSummary);

            my $item_showCurrentRegion = Gtk3::MenuItem->new('S_how current region');
            $item_showCurrentRegion->signal_connect('activate' => sub {

                $self->session->pseudoCmd(
                    'modelreport -r <' . $self->currentRegionmap->name . '>',
                    'show_all',
                );
            });
            $subMenu_reports->append($item_showCurrentRegion);
            # (Requires $self->currentRegionmap)
            $self->ivAdd('menuToolItemHash', 'report_region', $item_showCurrentRegion);

            $subMenu_reports->append(Gtk3::SeparatorMenuItem->new());  # Separator

                # 'Character visits' sub-submenu
                my $subSubMenu_visits = Gtk3::Menu->new();

                my $item_visits1 = Gtk3::MenuItem->new('_All regions/characters');
                $item_visits1->signal_connect('activate' => sub {

                    $self->session->pseudoCmd('modelreport -v', 'show_all');
                });
                $subSubMenu_visits->append($item_visits1);

                my $item_visits2 = Gtk3::MenuItem->new('Current _region');
                $item_visits2->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -v -r <' . $self->currentRegionmap->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_visits->append($item_visits2);
                # (Requires $self->currentRegionmap)
                $self->ivAdd('menuToolItemHash', 'report_visits_2', $item_visits2);

                my $item_visits3 = Gtk3::MenuItem->new('Current _character');
                $item_visits3->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -v -c <' . $self->session->currentChar->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_visits->append($item_visits3);
                # (Requires current character profile)
                $self->ivAdd('menuToolItemHash', 'report_visits_3', $item_visits3);

                my $item_visits4 = Gtk3::MenuItem->new('C_urrent region/character');
                $item_visits4->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -v -r <' . $self->currentRegionmap->name . '>' . ' -c <'
                        . $self->session->currentChar->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_visits->append($item_visits4);
                # (Requires $self->currentRegionmap and current character profile)
                $self->ivAdd('menuToolItemHash', 'report_visits_4', $item_visits4);

            my $item_visits = Gtk3::MenuItem->new('_Character visits');
            $item_visits->set_submenu($subSubMenu_visits);
            $subMenu_reports->append($item_visits);

                # 'Room guilds' sub-submenu
                my $subSubMenu_guilds = Gtk3::Menu->new();

                my $item_guilds1 = Gtk3::MenuItem->new('_All regions/guilds');
                $item_guilds1->signal_connect('activate' => sub {

                    $self->session->pseudoCmd('modelreport -g', 'show_all');
                });
                $subSubMenu_guilds->append($item_guilds1);

                my $item_guilds2 = Gtk3::MenuItem->new('Current _region');
                $item_guilds2->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -g -r <' . $self->currentRegionmap->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_guilds->append($item_guilds2);
                # (Requires $self->currentRegionmap)
                $self->ivAdd('menuToolItemHash', 'report_guilds_2', $item_guilds2);

                my $item_guilds3 = Gtk3::MenuItem->new('Current _guild');
                $item_guilds3->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -g -n <' . $self->session->currentGuild->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_guilds->append($item_guilds3);
                # (Requires current guild profile)
                $self->ivAdd('menuToolItemHash', 'report_guilds_3', $item_guilds3);

                my $item_guilds4 = Gtk3::MenuItem->new('C_urrent region/guild');
                $item_guilds4->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -g -r <' . $self->currentRegionmap->name . '>' . ' -n <'
                        . $self->session->currentGuild->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_guilds->append($item_guilds4);
                # (Requires $self->currentRegionmap and current guild profile)
                $self->ivAdd('menuToolItemHash', 'report_guilds_4', $item_guilds4);

            my $item_guilds = Gtk3::MenuItem->new('Room _guilds');
            $item_guilds->set_submenu($subSubMenu_guilds);
            $subMenu_reports->append($item_guilds);

                # 'Room flags' sub-submenu
                my $subSubMenu_roomFlags = Gtk3::Menu->new();

                my $item_roomFlags1 = Gtk3::MenuItem->new('_All regions/flags');
                $item_roomFlags1->signal_connect('activate' => sub {

                    $self->session->pseudoCmd('modelreport -f', 'show_all');
                });
                $subSubMenu_roomFlags->append($item_roomFlags1);

                my $item_roomFlags2 = Gtk3::MenuItem->new('Current _region');
                $item_roomFlags2->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -f -r <' . $self->currentRegionmap->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_roomFlags->append($item_roomFlags2);
                # (Requires $self->currentRegionmap)
                $self->ivAdd('menuToolItemHash', 'report_flags_2', $item_roomFlags2);

                my $item_roomFlags3 = Gtk3::MenuItem->new('_Specify flag...');
                $item_roomFlags3->signal_connect('activate' => sub {

                    my (
                        $choice,
                        @list,
                    );

                    @list = $self->worldModelObj->roomFlagOrderedList;

                    $choice = $self->showComboDialogue(
                        'Select room flag',
                        'Select one of the world model\'s room flags',
                        \@list,
                    );

                    if ($choice) {

                        $self->session->pseudoCmd(
                            'modelreport -f -l <' . $choice . '>',
                            'show_all',
                        );
                    }
                });
                $subSubMenu_roomFlags->append($item_roomFlags3);

                my $item_roomFlags4 = Gtk3::MenuItem->new('C_urrent region/specify flag...');
                $item_roomFlags4->signal_connect('activate' => sub {

                    my (
                        $choice,
                        @list,
                    );

                    @list = $self->worldModelObj->roomFlagOrderedList;

                    $choice = $self->showComboDialogue(
                        'Select room flag',
                        'Select one of the world model\'s room flags',
                        \@list,
                    );

                    if ($choice) {

                        $self->session->pseudoCmd(
                            'modelreport -f -r <' . $self->currentRegionmap->name . '>' . ' -l <'
                            . $choice . '>',
                            'show_all',
                        );
                    }
                });
                $subSubMenu_roomFlags->append($item_roomFlags4);
                # (Requires $self->currentRegionmap)
                $self->ivAdd('menuToolItemHash', 'report_flags_4', $item_roomFlags4);

            my $item_roomFlags = Gtk3::MenuItem->new('Room _flags');
            $item_roomFlags->set_submenu($subSubMenu_roomFlags);
            $subMenu_reports->append($item_roomFlags);

                 # 'Rooms' sub-submenu
                my $subSubMenu_rooms = Gtk3::Menu->new();

                my $item_rooms1 = Gtk3::MenuItem->new('_All regions');
                $item_rooms1->signal_connect('activate' => sub {

                    $self->session->pseudoCmd('modelreport -m', 'show_all');
                });
                $subSubMenu_rooms->append($item_rooms1);

                my $item_rooms2 = Gtk3::MenuItem->new('_Current region');
                $item_rooms2->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -m -r <' . $self->currentRegionmap->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_rooms->append($item_rooms2);
                # (Requires $self->currentRegionmap)
                $self->ivAdd('menuToolItemHash', 'report_rooms_2', $item_rooms2);

            my $item_rooms = Gtk3::MenuItem->new('_Rooms');
            $item_rooms->set_submenu($subSubMenu_rooms);
            $subMenu_reports->append($item_rooms);

                 # 'Exits' sub-submenu
                my $subSubMenu_exits = Gtk3::Menu->new();

                my $item_exits1 = Gtk3::MenuItem->new('_All regions');
                $item_exits1->signal_connect('activate' => sub {

                    $self->session->pseudoCmd('modelreport -x', 'show_all');
                });
                $subSubMenu_exits->append($item_exits1);

                my $item_exits2 = Gtk3::MenuItem->new('_Current region');
                $item_exits2->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -x -r <' . $self->currentRegionmap->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_exits->append($item_exits2);
                # (Requires $self->currentRegionmap)
                $self->ivAdd('menuToolItemHash', 'report_exits_2', $item_exits2);

            my $item_exits = Gtk3::MenuItem->new('_Exits');
            $item_exits->set_submenu($subSubMenu_exits);
            $subMenu_reports->append($item_exits);

                # 'Checked directions' sub-submenu
                my $subSubMenu_checked = Gtk3::Menu->new();

                my $item_checked1 = Gtk3::MenuItem->new('_All regions');
                $item_checked1->signal_connect('activate' => sub {

                    $self->session->pseudoCmd('modelreport -h', 'show_all');
                });
                $subSubMenu_checked->append($item_checked1);

                my $item_checked2 = Gtk3::MenuItem->new('_Current region');
                $item_checked2->signal_connect('activate' => sub {

                    $self->session->pseudoCmd(
                        'modelreport -h -r <' . $self->currentRegionmap->name . '>',
                        'show_all',
                    );
                });
                $subSubMenu_checked->append($item_checked2);
                # (Requires $self->currentRegionmap)
                $self->ivAdd('menuToolItemHash', 'report_checked_2', $item_checked2);

            my $item_checked = Gtk3::MenuItem->new('Checked _directions');
            $item_checked->set_submenu($subSubMenu_checked);
            $subMenu_reports->append($item_checked);

        my $item_reports = Gtk3::MenuItem->new('_Generate reports');
        $item_reports->set_submenu($subMenu_reports);
        $column_edit->append($item_reports);

        $column_edit->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Reset' sub-submenu
            my $subMenu_reset = Gtk3::Menu->new();

            my $item_resetRoomData = Gtk3::MenuItem->new('Reset _room data...');
            $item_resetRoomData->signal_connect('activate' => sub {

                $self->resetRoomDataCallback();
            });
            $subMenu_reset->append($item_resetRoomData);

            my $item_resetCharVisits = Gtk3::MenuItem->new('Reset _visits by character...');
            $item_resetCharVisits->signal_connect('activate' => sub {

                $self->resetVisitsCallback();
            });
            $subMenu_reset->append($item_resetCharVisits);

        my $item_reset = Gtk3::MenuItem->new('_Reset');
        $item_reset->set_submenu($subMenu_reset);
        $column_edit->append($item_reset);

        $column_edit->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_editDict = Gtk3::ImageMenuItem->new('Edit current _dictionary...');
        my $img_editDict = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editDict->set_image($img_editDict);
        $item_editDict->signal_connect('activate' => sub {

            # Open an 'edit' window for the current dictionary
            $self->createFreeWin(
                'Games::Axmud::EditWin::Dict',
                $self,
                $self->session,
                'Edit dictionary \'' . $self->session->currentDict->name . '\'',
                $self->session->currentDict,
                FALSE,          # Not temporary
            );
        });
        $column_edit->append($item_editDict);

        my $item_addWords = Gtk3::MenuItem->new('Add dictionary _words...');
        $item_addWords->signal_connect('activate' => sub {

            $self->createFreeWin(
                'Games::Axmud::OtherWin::QuickWord',
                $self,
                $self->session,
                'Quick word adder',
            );
        });
        $column_edit->append($item_addWords);

        my $item_updateModel = Gtk3::MenuItem->new('U_pdate model words');
        $item_updateModel->signal_connect('activate' => sub {

            # Use pseudo-command mode 'win_error' - show success messages in the 'main' window,
            #   error messages in 'dialogue' window
            $self->session->pseudoCmd('updatemodel -t', 'win_error');
        });
        $column_edit->append($item_updateModel);

        $column_edit->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_setupWizard = Gtk3::ImageMenuItem->new('Run _Locator wizard...');
        my $img_setupWizard = Gtk3::Image->new_from_stock('gtk-page-setup', 'menu');
        $item_setupWizard->set_image($img_setupWizard);
        $item_setupWizard->signal_connect('activate' => sub {

            if ($self->session->wizWin) {

                # Some kind of 'wiz' window is already open
                $self->session->wizWin->restoreFocus();

            } else {

                # Open the Locator wizard window
                $self->session->pseudoCmd('locatorwizard', $self->pseudoCmdMode);
            }
        });
        $column_edit->append($item_setupWizard);

        my $item_editModel = Gtk3::ImageMenuItem->new('Edit world _model...');
        my $img_editModel = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editModel->set_image($img_editModel);
        $item_editModel->signal_connect('activate' => sub {

            # Open an 'edit' window for the world model
            $self->createFreeWin(
                'Games::Axmud::EditWin::WorldModel',
                $self,
                $self->session,
                'Edit world model',
                $self->session->worldModelObj,
                FALSE,                          # Not temporary
            );
        });
        $column_edit->append($item_editModel);

        # Setup complete
        return $column_edit;
    }

    sub enableViewColumn {

        # Sets up the 'View' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Local variables
        my (
            $item_group,
            @magList, @shortMagList, @initList, @interiorList,
            %interiorHash,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableViewColumn', @_);
        }

        # Set up column
        my $column_view = Gtk3::Menu->new();
        if (! $column_view) {

            return undef;
        }

            # 'Window components' submenu
            my $subMenu_winComponents = Gtk3::Menu->new();

            my $item_showMenuBar = Gtk3::CheckMenuItem->new('Show menu_bar');
            $item_showMenuBar->set_active($self->worldModelObj->showMenuBarFlag);
            $item_showMenuBar->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleWinComponents(
                    'showMenuBarFlag',
                    $item_showMenuBar->get_active(),
                );
            });
            $subMenu_winComponents->append($item_showMenuBar);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'show_menu_bar', $item_showMenuBar);

            my $item_showToolbar = Gtk3::CheckMenuItem->new('Show _toolbar');
            $item_showToolbar->set_active($self->worldModelObj->showToolbarFlag);
            $item_showToolbar->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleWinComponents(
                    'showToolbarFlag',
                    $item_showToolbar->get_active(),
                );
            });
            $subMenu_winComponents->append($item_showToolbar);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'show_toolbar', $item_showToolbar);

            my $item_showTreeView = Gtk3::CheckMenuItem->new('Show _regions');
            $item_showTreeView->set_active($self->worldModelObj->showTreeViewFlag);
            $item_showTreeView->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleWinComponents(
                    'showTreeViewFlag',
                    $item_showTreeView->get_active(),
                );
            });
            $subMenu_winComponents->append($item_showTreeView);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'show_treeview', $item_showTreeView);

            my $item_showCanvas = Gtk3::CheckMenuItem->new('Show _map');
            $item_showCanvas->set_active($self->worldModelObj->showCanvasFlag);
            $item_showCanvas->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleWinComponents(
                    'showCanvasFlag',
                    $item_showCanvas->get_active(),
                );
            });
            $subMenu_winComponents->append($item_showCanvas);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'show_canvas', $item_showCanvas);

            $subMenu_winComponents->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_redrawWindow = Gtk3::MenuItem->new('Re_draw window');
            $item_redrawWindow->signal_connect('activate' => sub {

                $self->redrawWidgets('menu_bar', 'toolbar', 'treeview', 'canvas');
            });
            $subMenu_winComponents->append($item_redrawWindow);

        my $item_windowComponents = Gtk3::MenuItem->new('_Window components');
        $item_windowComponents->set_submenu($subMenu_winComponents);
        $column_view->append($item_windowComponents);

            # 'Current room' submenu
            my $subMenu_currentRoom = Gtk3::Menu->new();

            my $item_radio1 = Gtk3::RadioMenuItem->new_with_mnemonic(undef, 'Draw _normal room');
            $item_radio1->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio1->get_active()) {

                    $self->worldModelObj->switchMode(
                        'currentRoomMode',
                        'single',           # New value of ->currentRoomMode
                        FALSE,              # No call to ->redrawRegions; current room is redrawn
                        'normal_current_mode',
                    );
                }
            });
            my $item_group0 = $item_radio1->get_group();
            $subMenu_currentRoom->append($item_radio1);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'normal_current_mode', $item_radio1);

            my $item_radio2 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group0,
                'Draw _emphasised room',
            );
            if ($self->worldModelObj->currentRoomMode eq 'double') {

                $item_radio2->set_active(TRUE);
            }
            $item_radio2->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio2->get_active()) {

                    $self->worldModelObj->switchMode(
                        'currentRoomMode',
                        'double',           # New value of ->currentRoomMode
                        FALSE,              # No call to ->redrawRegions; current room is redrawn
                        'empahsise_current_room',
                    );
                }
            });
            $subMenu_currentRoom->append($item_radio2);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'empahsise_current_room', $item_radio2);

            my $item_radio3 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group0,
                'Draw _filled-in room',
            );
            if ($self->worldModelObj->currentRoomMode eq 'interior') {

                $item_radio3->set_active(TRUE);
            }
            $item_radio3->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio3->get_active()) {

                    $self->worldModelObj->switchMode(
                        'currentRoomMode',
                        'interior',         # New value of ->currentRoomMode
                        FALSE,              # No call to ->redrawRegions; current room is redrawn
                        'fill_in_current_room',
                    );
                }
            });
            $subMenu_currentRoom->append($item_radio3);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'fill_in_current_room', $item_radio3);

        my $item_currentRoom = Gtk3::MenuItem->new('_Draw current room');
        $item_currentRoom->set_submenu($subMenu_currentRoom);
        $column_view->append($item_currentRoom);

            # 'Room filters' submenu
            my $subMenu_roomFilters = Gtk3::Menu->new();

            my $item_releaseAllFilters = Gtk3::CheckMenuItem->new('_Release all filters');
            $item_releaseAllFilters->set_active($self->worldModelObj->allRoomFiltersFlag);
            $item_releaseAllFilters->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'allRoomFiltersFlag',
                        $item_releaseAllFilters->get_active(),
                        TRUE,      # Do call $self->redrawRegions
                        'release_all_filters',
                        'icon_release_all_filters',
                    );
                }
            });
            $subMenu_roomFilters->append($item_releaseAllFilters);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'release_all_filters', $item_releaseAllFilters);

            $subMenu_roomFilters->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my @shortcutList = $axmud::CLIENT->constRoomFilterKeyList;
            foreach my $filter ($axmud::CLIENT->constRoomFilterList) {

                my $shortcut = shift @shortcutList;

                my $menuItem = Gtk3::CheckMenuItem->new('Release ' . $shortcut . ' filter');
                $menuItem->set_active($self->worldModelObj->ivShow('roomFilterApplyHash', $filter));
                $menuItem->signal_connect('toggled' => sub {

                    if (! $self->ignoreMenuUpdateFlag) {

                        $self->worldModelObj->toggleFilter(
                            $filter,
                            $menuItem->get_active(),
                        );
                    }
                });
                $subMenu_roomFilters->append($menuItem);
                # (Never desensitised)
                $self->ivAdd('menuToolItemHash', $filter . '_filter', $menuItem);
            }

        my $item_roomFilters = Gtk3::MenuItem->new('Room _filters');
        $item_roomFilters->set_submenu($subMenu_roomFilters);
        $column_view->append($item_roomFilters);

            # 'Room interiors' submenu
            my $subMenu_roomInteriors = Gtk3::Menu->new();

            @initList = (
                'none' => '_Don\'t draw counts',
                'shadow_count' => 'Draw _unallocated/shadow exits',
                'region_count' => 'Draw re_gion/super region exits',
                'checked_count' => 'Draw _checked/checkable directions',
                'room_content' => 'Draw _room contents',
                'hidden_count' => 'Draw _hidden contents',
                'temp_count' => 'Draw _temporary contents',
                'word_count' => 'Draw r_ecognised words',
                'room_tag' => 'Draw room t_ag',
                'room_flag' => 'Draw r_oom flag text',
                'visit_count' => 'Draw character _visits',
                'compare_count' => 'Draw _matching rooms',
                'profile_count' => 'Draw e_xclusive profiles',
                'title_descrip' => 'Draw t_itles/descriptions',
                'exit_pattern' => 'Draw exit _patterns',
                'source_code' => 'Draw room _source code',
                'grid_posn' => 'Dra_w grid coordinates',
                'vnum' => 'Draw world\'s room v_num',
            );

            do {

                my ($mode, $descrip);

                $mode = shift @initList;
                $descrip = shift @initList;

                push (@interiorList, $mode);
                $interiorHash{$mode} = $descrip;

            } until (! @initList);

            for (my $count = 0; $count < (scalar @interiorList); $count++) {

                my ($icon, $mode);

                $mode = $interiorList[$count];

                # (For $count = 0, $item_group is 'undef')
                my $item_radio = Gtk3::RadioMenuItem->new_with_mnemonic(
                    $item_group,
                    $interiorHash{$mode},
                );
                if ($self->worldModelObj->roomInteriorMode eq $mode) {

                    $item_radio->set_active(TRUE);
                }

                $item_radio->signal_connect('toggled' => sub {

                    if (! $self->ignoreMenuUpdateFlag && $item_radio->get_active()) {

                        $self->worldModelObj->switchRoomInteriorMode($mode);
                    }
                });
                $item_group = $item_radio->get_group();
                $subMenu_roomInteriors->append($item_radio);
                # (Never desensitised)
                $self->ivAdd('menuToolItemHash', 'interior_mode_' . $mode, $item_radio);
            }

            $subMenu_roomInteriors->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_changeCharDrawn = Gtk3::MenuItem->new('Ch_ange character drawn...');
            $item_changeCharDrawn->signal_connect('activate' => sub {

                # (Callback func has no dependencies)
                $self->changeCharDrawnCallback();
            });
            $subMenu_roomInteriors->append($item_changeCharDrawn);

        my $item_roomInteriors = Gtk3::MenuItem->new('R_oom interiors');
        $item_roomInteriors->set_submenu($subMenu_roomInteriors);
        $column_view->append($item_roomInteriors);

            # 'All exits' submenu
            my $subMenu_allExits = Gtk3::Menu->new();

            my $item_radio11 = Gtk3::RadioMenuItem->new_with_mnemonic(
                undef,
                '_Use region exit settings',
            );
            $item_radio11->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio11->get_active()) {

                    $self->worldModelObj->switchMode(
                        'drawExitMode',
                        'ask_regionmap',    # New value of ->drawExitMode
                        TRUE,               # Do call $self->redrawRegions
                        'draw_defer_exits',
                        'icon_draw_defer_exits',
                    );
                }
            });
            my $item_group1 = $item_radio11->get_group();
            $subMenu_allExits->append($item_radio11);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_defer_exits', $item_radio11);

            my $item_radio12 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group1,
                'Draw _no exits',
            );
            if ($self->worldModelObj->drawExitMode eq 'no_exit') {

                $item_radio12->set_active(TRUE);
            }
            $item_radio12->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio12->get_active()) {

                    $self->worldModelObj->switchMode(
                        'drawExitMode',
                        'no_exit',          # New value of ->drawExitMode
                        TRUE,               # Do call $self->redrawRegions
                        'draw_no_exits',
                        'icon_draw_no_exits',
                    );
                }
            });
            $subMenu_allExits->append($item_radio12);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_no_exits', $item_radio12);

            my $item_radio13 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group1,
                'Draw _simple exits',
            );
            if ($self->worldModelObj->drawExitMode eq 'simple_exit') {

                $item_radio13->set_active(TRUE);
            }
            $item_radio13->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio13->get_active()) {

                    $self->worldModelObj->switchMode(
                        'drawExitMode',
                        'simple_exit',      # New value of ->drawExitMode
                        TRUE,               # Do call $self->redrawRegions
                        'draw_simple_exits',
                        'icon_draw_simple_exits',
                    );
                }
            });
            $subMenu_allExits->append($item_radio13);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_simple_exits', $item_radio13);

            my $item_radio14 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group1,
                'Draw _complex exits',
            );
            if ($self->worldModelObj->drawExitMode eq 'complex_exit') {

                $item_radio14->set_active(TRUE);
            }
            $item_radio14->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio14->get_active()) {

                    $self->worldModelObj->switchMode(
                        'drawExitMode',
                        'complex_exit',     # New value of ->drawExitMode
                        TRUE,               # Do call $self->redrawRegions
                        'draw_complex_exits',
                        'icon_draw_complex_exits',
                    );
                }
            });
            $subMenu_allExits->append($item_radio14);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_complex_exits', $item_radio14);

            $subMenu_allExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_obscuredExits = Gtk3::CheckMenuItem->new('_Obscure unimportant exits');
            $item_obscuredExits->set_active($self->worldModelObj->obscuredExitFlag);
            $item_obscuredExits->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'obscuredExitFlag',
                        $item_obscuredExits->get_active(),
                        TRUE,      # Do call $self->redrawRegions
                        'obscured_exits',
                        'icon_obscured_exits',
                    );
                }
            });
            $subMenu_allExits->append($item_obscuredExits);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'obscured_exits', $item_obscuredExits);

            my $item_autoRedraw = Gtk3::CheckMenuItem->new('_Auto-redraw obscured exits');
            $item_autoRedraw->set_active($self->worldModelObj->obscuredExitRedrawFlag);
            $item_autoRedraw->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'obscuredExitRedrawFlag',
                        $item_autoRedraw->get_active(),
                        TRUE,      # Do call $self->redrawRegions
                        'auto_redraw_obscured',
                        'icon_auto_redraw_obscured',
                    );
                }
            });
            $subMenu_allExits->append($item_autoRedraw);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_redraw_obscured', $item_autoRedraw);

            my $item_obscuredExitRadius = Gtk3::MenuItem->new('Set obscure _radius...');
            $item_obscuredExitRadius->signal_connect('activate' => sub {

                $self->obscuredRadiusCallback();
            });
            $subMenu_allExits->append($item_obscuredExitRadius);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'obscured_exit_radius', $item_obscuredExitRadius);

            $subMenu_allExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_drawOrnaments = Gtk3::CheckMenuItem->new('Draw exit orna_ments');
            $item_drawOrnaments->set_active($self->worldModelObj->drawOrnamentsFlag);
            $item_drawOrnaments->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'drawOrnamentsFlag',
                        $item_drawOrnaments->get_active(),
                        TRUE,      # Do call $self->redrawRegions
                        'draw_ornaments',
                        'icon_draw_ornaments',
                    );
                }
            });
            $subMenu_allExits->append($item_drawOrnaments);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_ornaments', $item_drawOrnaments);

        my $item_allExits = Gtk3::MenuItem->new('Exits (_all regions)');
        $item_allExits->set_submenu($subMenu_allExits);
        $column_view->append($item_allExits);

            # 'Region exits' submenu
            my $subMenu_regionExits = Gtk3::Menu->new();

            my $item_radio21 = Gtk3::RadioMenuItem->new_with_mnemonic(undef, 'Draw _no exits');
            $item_radio21->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio21->get_active()) {

                    $self->worldModelObj->switchRegionDrawExitMode(
                        $self->currentRegionmap,
                        'no_exit',
                    );
                }
            });
            my $item_group2 = $item_radio21->get_group();
            $subMenu_regionExits->append($item_radio21);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'region_draw_no_exits', $item_radio21);

            my $item_radio22 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group2,
                'Draw _simple exits',
            );
            if ($self->currentRegionmap && $self->currentRegionmap->drawExitMode eq 'simple_exit') {

                $item_radio22->set_active(TRUE);
            }
            $item_radio22->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio22->get_active()) {

                    $self->worldModelObj->switchRegionDrawExitMode(
                        $self->currentRegionmap,
                        'simple_exit',
                    );
                }
            });
            $subMenu_regionExits->append($item_radio22);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'region_draw_simple_exits', $item_radio22);

            my $item_radio23 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group2,
                'Draw _complex exits',
            );
            if (
                $self->currentRegionmap
                && $self->currentRegionmap->drawExitMode eq 'complex_exit'
            ) {
                $item_radio23->set_active(TRUE);
            }
            $item_radio23->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio23->get_active()) {

                    $self->worldModelObj->switchRegionDrawExitMode(
                        $self->currentRegionmap,
                        'complex_exit',
                    );
                }
            });
            $subMenu_regionExits->append($item_radio23);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'region_draw_complex_exits', $item_radio23);

            $subMenu_regionExits->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_obscuredExitsRegion = Gtk3::CheckMenuItem->new('_Obscure unimportant exits');
            if ($self->currentRegionmap) {

                $item_obscuredExitsRegion->set_active($self->currentRegionmap->obscuredExitFlag);
            }
            $item_obscuredExitsRegion->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleObscuredExitFlag($self->currentRegionmap);
                }
            });
            $subMenu_regionExits->append($item_obscuredExitsRegion);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'obscured_exits_region', $item_obscuredExitsRegion);

            my $item_autoRedrawRegion = Gtk3::CheckMenuItem->new('_Auto-redraw obscured exits');
            if ($self->currentRegionmap) {

                $item_autoRedrawRegion->set_active($self->currentRegionmap->obscuredExitRedrawFlag);
            }
            $item_autoRedrawRegion->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleObscuredExitRedrawFlag($self->currentRegionmap);
                }
            });
            $subMenu_regionExits->append($item_autoRedrawRegion);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_redraw_obscured_region', $item_autoRedrawRegion);

            my $item_obscuredExitRadiusRegion = Gtk3::MenuItem->new('Set obscure _radius...');
            $item_obscuredExitRadiusRegion->signal_connect('activate' => sub {

                $self->obscuredRadiusCallback($self->currentRegionmap);
            });
            $subMenu_regionExits->append($item_obscuredExitRadiusRegion);
            # (Never desensitised)
            $self->ivAdd(
                'menuToolItemHash',
                'obscured_exit_radius_region',
                $item_obscuredExitRadiusRegion,
            );

            $subMenu_regionExits->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_drawOrnamentsRegion = Gtk3::CheckMenuItem->new('Draw exit orna_ments');
            if ($self->currentRegionmap) {

                $item_drawOrnamentsRegion->set_active($self->currentRegionmap->drawOrnamentsFlag);
            }
            $item_drawOrnamentsRegion->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleDrawOrnamentsFlag($self->currentRegionmap);
                }
            });
            $subMenu_regionExits->append($item_drawOrnamentsRegion);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_ornaments_region', $item_drawOrnamentsRegion);

        my $item_regionExits = Gtk3::MenuItem->new('Exits (_current region)');
        $item_regionExits->set_submenu($subMenu_regionExits);
        $column_view->append($item_regionExits);
        # (Requires $self->currentRegionmap and $self->worldModelObj->drawExitMode is
        #   'ask_regionmap')
        $self->ivAdd('menuToolItemHash', 'draw_region_exits', $item_regionExits);

        $column_view->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_zoomIn = Gtk3::ImageMenuItem->new('Zoom i_n');
        my $img_zoomIn = Gtk3::Image->new_from_stock('gtk-zoom-in', 'menu');
        $item_zoomIn->set_image($img_zoomIn);
        $item_zoomIn->signal_connect('activate' => sub {

            $self->zoomCallback('in');
        });
        $column_view->append($item_zoomIn);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'zoom_in', $item_zoomIn);

        my $item_zoomOut = Gtk3::ImageMenuItem->new('Zoom _out');
        my $img_zoomOut = Gtk3::Image->new_from_stock('gtk-zoom-out', 'menu');
        $item_zoomOut->set_image($img_zoomOut);
        $item_zoomOut->signal_connect('activate' => sub {

            $self->zoomCallback('out');
        });
        $column_view->append($item_zoomOut);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'zoom_out', $item_zoomOut);

            # 'Zoom' submenu
            my $subMenu_zoom = Gtk3::Menu->new();

            # Import the list of magnifications
            @magList = $self->constMagnifyList;
            # Use a subset of magnifications from $self->constMagnifyList (and in reverse order to
            #   that found in $self->constMagnifyList)
            @shortMagList = reverse $self->constShortMagnifyList;

            foreach my $mag (@shortMagList) {

                my $menuItem = Gtk3::MenuItem->new('Zoom ' . $mag * 100 . '%');
                $menuItem->signal_connect('activate' => sub {

                    # No argument causes the called function to prompt the user
                    $self->zoomCallback($mag);
                });
                $subMenu_zoom->append($menuItem);
            }

            $subMenu_zoom->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_zoomMax = Gtk3::MenuItem->new('Zoom _in max');
            $item_zoomMax->signal_connect('activate' => sub {

                $self->zoomCallback($magList[-1]);
            });
            $subMenu_zoom->append($item_zoomMax);

            my $item_zoomMin = Gtk3::MenuItem->new('Zoom _out max');
            $item_zoomMin->signal_connect('activate' => sub {

                $self->zoomCallback($magList[0]);
            });
            $subMenu_zoom->append($item_zoomMin);

            $subMenu_zoom->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_zoomPrompt = Gtk3::MenuItem->new('O_ther...');
            $item_zoomPrompt->signal_connect('activate' => sub {

                # No argument causes the called function to prompt the user
                $self->zoomCallback();
            });
            $subMenu_zoom->append($item_zoomPrompt);

        my $item_zoom = Gtk3::ImageMenuItem->new('_Zoom');
        my $img_zoom = Gtk3::Image->new_from_stock('gtk-zoom-fit', 'menu');
        $item_zoom->set_image($img_zoom);
        $item_zoom->set_submenu($subMenu_zoom);
        $column_view->append($item_zoom);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'zoom_sub', $item_zoom);

        $column_view->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Level' submenu
            my $subMenu_level = Gtk3::Menu->new();

            my $item_moveUpLevel = Gtk3::MenuItem->new('Move _up level');
            $item_moveUpLevel->signal_connect('activate' => sub {

                $self->setCurrentLevel($self->currentRegionmap->currentLevel + 1);

                # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
                $self->restrictWidgets();
            });
            $subMenu_level->append($item_moveUpLevel);
            # (Requires $self->currentRegionmap)
            $self->ivAdd('menuToolItemHash', 'move_up_level', $item_moveUpLevel);

            my $item_moveDownLevel = Gtk3::MenuItem->new('Move _down level');
            $item_moveDownLevel->signal_connect('activate' => sub {

                $self->setCurrentLevel($self->currentRegionmap->currentLevel - 1);

                # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
                $self->restrictWidgets();
            });
            $subMenu_level->append($item_moveDownLevel);
            # (Requires $self->currentRegionmap)
            $self->ivAdd('menuToolItemHash', 'move_down_level', $item_moveDownLevel);

            $subMenu_level->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_changeLevel = Gtk3::MenuItem->new('_Change level...');
            $item_changeLevel->signal_connect('activate' => sub {

                $self->changeLevelCallback();
            });
            $subMenu_level->append($item_changeLevel);

        my $item_level = Gtk3::MenuItem->new('_Level');
        $item_level->set_submenu($subMenu_level);
        $column_view->append($item_level);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'level_sub', $item_level);

        $column_view->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Centre map' submenu
            my $subMenu_centreMap = Gtk3::Menu->new();

            my $item_centreMap_currentRoom = Gtk3::MenuItem->new('_Current room');
            $item_centreMap_currentRoom->signal_connect('activate' => sub {

                $self->centreMapOverRoom($self->mapObj->currentRoom);
            });
            $subMenu_centreMap->append($item_centreMap_currentRoom);
            # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
            $self->ivAdd(
                'menuToolItemHash',
                'centre_map_current_room',
                $item_centreMap_currentRoom,
            );

            my $item_centreMap_selectRoom = Gtk3::MenuItem->new('_Selected room');
            $item_centreMap_selectRoom->signal_connect('activate' => sub {

                $self->centreMapOverRoom($self->selectedRoom);
            });
            $subMenu_centreMap->append($item_centreMap_selectRoom);
            # (Requires $self->currentRegionmap & $self->selectedRoom)
            $self->ivAdd(
                'menuToolItemHash',
                'centre_map_select_room',
                $item_centreMap_selectRoom,
            );

            my $item_centreMap_lastKnownRoom = Gtk3::MenuItem->new('_Last known room');
            $item_centreMap_lastKnownRoom->signal_connect('activate' => sub {

                $self->centreMapOverRoom($self->mapObj->lastKnownRoom);
            });
            $subMenu_centreMap->append($item_centreMap_lastKnownRoom);
            # (Requires $self->currentRegionmap & $self->mapObj->lastknownRoom)
            $self->ivAdd(
                'menuToolItemHash',
                'centre_map_last_known_room',
                $item_centreMap_lastKnownRoom,
            );

            $subMenu_centreMap->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_centreMap_middleGrid = Gtk3::MenuItem->new('_Middle of grid');
            $item_centreMap_middleGrid->signal_connect('activate' => sub {

                $self->setMapPosn(0.5, 0.5);
            });
            $subMenu_centreMap->append($item_centreMap_middleGrid);
            # (Requires $self->currentRegionmap)
            $self->ivAdd('menuToolItemHash', 'centre_map_middle_grid', $item_centreMap_middleGrid);

        my $item_centreMap = Gtk3::MenuItem->new('Centre _map');
        $item_centreMap->set_submenu($subMenu_centreMap);
        $column_view->append($item_centreMap);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'centre_map_sub', $item_centreMap);

        my $item_repositionAllMaps = Gtk3::MenuItem->new('_Reposition all maps');
        $item_repositionAllMaps->signal_connect('activate' => sub {

            $self->worldModelObj->repositionMaps();
        });
        $column_view->append($item_repositionAllMaps);

            # 'Tracking' submenu
            my $subMenu_tracking = Gtk3::Menu->new();

            my $item_trackCurrentRoom = Gtk3::CheckMenuItem->new('_Track current room');
            $item_trackCurrentRoom->set_active($self->worldModelObj->trackPosnFlag);
            $item_trackCurrentRoom->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'trackPosnFlag',
                        $item_trackCurrentRoom->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'track_current_room',
                        'icon_track_current_room',
                    );
                }
            });
            $subMenu_tracking->append($item_trackCurrentRoom);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'track_current_room', $item_trackCurrentRoom);

            $subMenu_tracking->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_radio31 = Gtk3::RadioMenuItem->new_with_mnemonic(undef, '_Always track');
            if (
                $self->worldModelObj->trackingSensitivity != 0.33
                && $self->worldModelObj->trackingSensitivity != 0.66
                && $self->worldModelObj->trackingSensitivity != 1
            ) {
                # Only the sensitivity values 0, 0.33, 0.66 and 1 are curently allowed; act as
                #   though the IV was set to 0
                $item_radio31->set_active(TRUE);
            }
            $item_radio31->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio31->get_active()) {

                    $self->worldModelObj->setTrackingSensitivity(0);
                }
            });
            my $item_group3 = $item_radio31->get_group();
            $subMenu_tracking->append($item_radio31);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'track_always', $item_radio31);

            my $item_radio32 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group3,
                'Track near _centre',
            );
            if ($self->worldModelObj->trackingSensitivity == 0.33) {

                $item_radio32->set_active(TRUE);
            }
            $item_radio32->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio32->get_active()) {

                    $self->worldModelObj->setTrackingSensitivity(0.33);
                }
            });
            $subMenu_tracking->append($item_radio32);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'track_near_centre', $item_radio32);

            my $item_radio33 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group3,
                'Track near _edge',
            );
            if ($self->worldModelObj->trackingSensitivity == 0.66) {

                $item_radio33->set_active(TRUE);
            }
            $item_radio33->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio33->get_active()) {

                    $self->worldModelObj->setTrackingSensitivity(0.66);
                }
            });
            $subMenu_tracking->append($item_radio33);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'track_near_edge', $item_radio33);

            my $item_radio34 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_group3,
                'Track if not _visible',
            );
            if ($self->worldModelObj->trackingSensitivity == 1) {

                $item_radio34->set_active(TRUE);
            }
            $item_radio34->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $item_radio34->get_active()) {

                    $self->worldModelObj->setTrackingSensitivity(1);
                }
            });
            $subMenu_tracking->append($item_radio34);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'track_not_visible', $item_radio34);

        my $item_tracking = Gtk3::MenuItem->new('_Tracking');
        $item_tracking->set_submenu($subMenu_tracking);
        $column_view->append($item_tracking);

        # Setup complete
        return $column_view;
    }

    sub enableModeColumn {

        # Called by $self->enableMenu
        # Sets up the 'Mode' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableModeColumn', @_);
        }

        # Set up column
        my $column_mode = Gtk3::Menu->new();
        if (! $column_mode) {

            return undef;
        }

        # (Save each radio menu item in a hash IV, so that when $self->setMode is called, the radio
        #   group can be toggled)
        my $item_radio1 = Gtk3::RadioMenuItem->new_with_mnemonic(undef, '_Wait mode');
        $item_radio1->signal_connect('toggled' => sub {

            # (To stop the equivalent toolbar icon from being toggled by the call to ->setMode,
            #   make use of $self->ignoreMenuUpdateFlag)
            if ($item_radio1->get_active && ! $self->ignoreMenuUpdateFlag) {

                $self->setMode('wait');
            }
        });
        my $item_group = $item_radio1->get_group();
        $column_mode->append($item_radio1);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'set_wait_mode', $item_radio1);

        my $item_radio2 = Gtk3::RadioMenuItem->new_with_mnemonic($item_group, '_Follow mode');
        $item_radio2->signal_connect('toggled' => sub {

            if ($item_radio2->get_active && ! $self->ignoreMenuUpdateFlag) {

                $self->setMode('follow');
            }
        });
        $column_mode->append($item_radio2);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'set_follow_mode', $item_radio2);

        my $item_radio3 = Gtk3::RadioMenuItem->new_with_mnemonic($item_group, '_Update mode');
        $item_radio3->signal_connect('toggled' => sub {

            if ($item_radio3->get_active && ! $self->ignoreMenuUpdateFlag) {

                $self->setMode('update');
            }
        });
        $column_mode->append($item_radio3);
        # (Requires $self->currentRegionmap, GA::Obj::WorldModel->disableUpdateModeFlag set to
        #   FALSE and a session not in 'connect offline' mode
        $self->ivAdd('menuToolItemHash', 'set_update_mode', $item_radio3);

        $column_mode->append(Gtk3::SeparatorMenuItem->new());   # Separator

        my $item_dragMode = Gtk3::CheckMenuItem->new('_Drag mode');
        $item_dragMode->set_active($self->dragModeFlag);
        $item_dragMode->signal_connect('toggled' => sub {

            if ($item_dragMode->get_active()) {
                $self->ivPoke('dragModeFlag', TRUE);
            } else {
                $self->ivPoke('dragModeFlag', FALSE);
            }

            # Set the equivalent toolbar button
            if ($self->ivExists('menuToolItemHash', 'icon_drag_mode')) {

                my $menuItem = $self->ivShow('menuToolItemHash', 'icon_drag_mode');
                $menuItem->set_active($item_dragMode->get_active());
            }
        });
        $column_mode->append($item_dragMode);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'drag_mode', $item_dragMode);

        my $item_graffitMode = Gtk3::CheckMenuItem->new('_Graffiti mode');
        $item_graffitMode->set_active($self->graffitiModeFlag);
        $item_graffitMode->signal_connect('toggled' => sub {

            my @redrawList;

            if ($item_graffitMode->get_active()) {

                $self->ivPoke('graffitiModeFlag', TRUE);

                # Tag current room, if any
                if ($self->mapObj->currentRoom) {

                    $self->ivAdd('graffitiHash', $self->mapObj->currentRoom->number);
                    $self->markObjs('room', $self->mapObj->currentRoom);
                    $self->doDraw();
                }

                # Initialise graffitied room counts
                $self->setWinTitle();

            } else {

                $self->ivPoke('graffitiModeFlag', FALSE);

                foreach my $num ($self->ivKeys('graffitiHash')) {

                    my $roomObj = $self->worldModelObj->ivShow('modelHash', $num);
                    if ($roomObj) {

                        push (@redrawList, 'room', $self->worldModelObj->ivShow('modelHash', $num));
                    }
                }

                $self->ivEmpty('graffitiHash');

                # Redraw any graffitied rooms
                if (@redrawList) {

                    $self->markObjs(@redrawList);
                    $self->doDraw();
                }

                # Remove graffitied room counts
                $self->setWinTitle();
            }

            # Set the equivalent toolbar button
            if ($self->ivExists('menuToolItemHash', 'icon_graffiti_mode')) {

                my $menuItem = $self->ivShow('menuToolItemHash', 'icon_graffiti_mode');
                $menuItem->set_active($item_graffitMode->get_active());
            }

            # The menu items which toggle graffiti in selected rooms are desensitised if
            #   ->graffitiModeFlag is FALSE
            # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
            $self->restrictWidgets();
        });
        $column_mode->append($item_graffitMode);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'graffiti_mode', $item_graffitMode);

        $column_mode->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Match rooms' submenu
            my $subMenu_matchRooms = Gtk3::Menu->new();

            my $item_matchTitle = Gtk3::CheckMenuItem->new('Match room _titles');
            $item_matchTitle->set_active($self->worldModelObj->matchTitleFlag);
            $item_matchTitle->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'matchTitleFlag',
                        $item_matchTitle->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'match_title',
                    );
                }
            });
            $subMenu_matchRooms->append($item_matchTitle);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'match_title', $item_matchTitle);

            my $item_matchDescrip = Gtk3::CheckMenuItem->new('Match room _descriptions');
            $item_matchDescrip->set_active($self->worldModelObj->matchDescripFlag);
            $item_matchDescrip->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'matchDescripFlag',
                        $item_matchDescrip->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'match_descrip',
                    );
                }
            });
            $subMenu_matchRooms->append($item_matchDescrip);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'match_descrip', $item_matchDescrip);

            my $item_matchExit = Gtk3::CheckMenuItem->new('Match _exits');
            $item_matchExit->set_active($self->worldModelObj->matchExitFlag);
            $item_matchExit->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'matchExitFlag',
                        $item_matchExit->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'match_exit',
                    );
                }
            });
            $subMenu_matchRooms->append($item_matchExit);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'match_exit', $item_matchExit);

            my $item_matchSource = Gtk3::CheckMenuItem->new('Match _source code');
            $item_matchSource->set_active($self->worldModelObj->matchSourceFlag);
            $item_matchSource->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'matchSourceFlag',
                        $item_matchSource->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'match_source',
                    );
                }
            });
            $subMenu_matchRooms->append($item_matchSource);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'match_source', $item_matchSource);

            my $item_matchVNum = Gtk3::CheckMenuItem->new('Match room _vnum');
            $item_matchVNum->set_active($self->worldModelObj->matchVNumFlag);
            $item_matchVNum->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'matchVNumFlag',
                        $item_matchVNum->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'match_vnum',
                    );
                }
            });
            $subMenu_matchRooms->append($item_matchVNum);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'match_vnum', $item_matchVNum);

            $subMenu_matchRooms->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_verboseChars = Gtk3::MenuItem->new('Set description _length...');
            $item_verboseChars->signal_connect('activate' => sub {

                $self->verboseCharsCallback();
            });
            $subMenu_matchRooms->append($item_verboseChars);

        my $item_matchRooms = Gtk3::MenuItem->new('_Match rooms');
        $item_matchRooms->set_submenu($subMenu_matchRooms);
        $column_mode->append($item_matchRooms);

            # 'Update rooms' submenu
            my $subMenu_updateRooms = Gtk3::Menu->new();

            my $item_updateTitle = Gtk3::CheckMenuItem->new('Update room _titles');
            $item_updateTitle->set_active($self->worldModelObj->updateTitleFlag);
            $item_updateTitle->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'updateTitleFlag',
                        $item_updateTitle->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'update_title',
                    );
                }
            });
            $subMenu_updateRooms->append($item_updateTitle);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'update_title', $item_updateTitle);

            my $item_updateDescrip = Gtk3::CheckMenuItem->new('Update room _descriptions');
            $item_updateDescrip->set_active($self->worldModelObj->updateDescripFlag);
            $item_updateDescrip->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'updateDescripFlag',
                        $item_updateDescrip->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'update_descrip',
                    );
                }
            });
            $subMenu_updateRooms->append($item_updateDescrip);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'update_descrip', $item_updateDescrip);

            my $item_updateExit = Gtk3::CheckMenuItem->new('Update _exits');
            $item_updateExit->set_active($self->worldModelObj->updateExitFlag);
            $item_updateExit->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'updateExitFlag',
                        $item_updateExit->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'update_exit',
                    );
                }
            });
            $subMenu_updateRooms->append($item_updateExit);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'update_exit', $item_updateExit);

            my $item_updateOrnament
                = Gtk3::CheckMenuItem->new('Update _ornaments from exit state');
            $item_updateOrnament->set_active($self->worldModelObj->updateOrnamentFlag);
            $item_updateOrnament->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'updateOrnamentFlag',
                        $item_updateOrnament->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'update_ornament',
                    );
                }
            });
            $subMenu_updateRooms->append($item_updateOrnament);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'update_ornament', $item_updateOrnament);

            my $item_updateSource = Gtk3::CheckMenuItem->new('Update _source code');
            $item_updateSource->set_active($self->worldModelObj->updateSourceFlag);
            $item_updateSource->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'updateSourceFlag',
                        $item_updateSource->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'update_source',
                    );
                }
            });
            $subMenu_updateRooms->append($item_updateSource);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'update_source', $item_updateSource);

            my $item_updateVNum = Gtk3::CheckMenuItem->new('Update room _vnum, etc');
            $item_updateVNum->set_active($self->worldModelObj->updateVNumFlag);
            $item_updateVNum->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'updateVNumFlag',
                        $item_updateVNum->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'update_vnum',
                    );
                }
            });
            $subMenu_updateRooms->append($item_updateVNum);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'update_vnum', $item_updateVNum);

            my $item_updateRoomCmd = Gtk3::CheckMenuItem->new('Update room _commands');
            $item_updateRoomCmd->set_active($self->worldModelObj->updateRoomCmdFlag);
            $item_updateRoomCmd->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'updateRoomCmdFlag',
                        $item_updateRoomCmd->get_active(),
                        FALSE,          # Do call $self->redrawRegions
                        'update_room_cmd',
                    );
                }
            });
            $subMenu_updateRooms->append($item_updateRoomCmd);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'update_room_cmd', $item_updateRoomCmd);

            $subMenu_updateRooms->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_analyseDescrip = Gtk3::CheckMenuItem->new('_Analyse room descrips');
            $item_analyseDescrip->set_active($self->worldModelObj->analyseDescripFlag);
            $item_analyseDescrip->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'analyseDescripFlag',
                        $item_analyseDescrip->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'analyse_descrip',
                    );
                }
            });
            $subMenu_updateRooms->append($item_analyseDescrip);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'analyse_descrip', $item_analyseDescrip);

        my $item_updateRooms = Gtk3::MenuItem->new('Update _rooms');
        $item_updateRooms->set_submenu($subMenu_updateRooms);
        $column_mode->append($item_updateRooms);

        $column_mode->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Painter' submenu
            my $subMenu_painter = Gtk3::Menu->new();

            my $item_painterEnabled = Gtk3::CheckMenuItem->new('_Painter enabled');
            $item_painterEnabled->set_active($self->painterFlag);
            $item_painterEnabled->signal_connect('toggled' => sub {

                my $item;

                # Toggle the flag
                if ($item_painterEnabled->get_active()) {
                    $self->ivPoke('painterFlag', TRUE);
                } else {
                    $self->ivPoke('painterFlag', FALSE);
                }

                # Update the corresponding toolbar icon
                $item = $self->ivShow('menuToolItemHash', 'icon_enable_painter');
                if ($item) {

                    $item->set_active($self->painterFlag);
                }
            });
            $subMenu_painter->append($item_painterEnabled);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'enable_painter', $item_painterEnabled);

            $subMenu_painter->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_paintAll = Gtk3::RadioMenuItem->new_with_mnemonic(undef, 'Paint _all rooms');
            $item_paintAll->signal_connect('toggled' => sub {

                if ($item_paintAll->get_active) {

                    $self->worldModelObj->set_paintAllRoomsFlag(TRUE);

                    # Set the equivalent toolbar button
                    if ($self->ivExists('menuToolItemHash', 'icon_paint_all')) {

                        $self->ivShow('menuToolItemHash', 'icon_paint_all')->set_active(TRUE);
                    }
                }
            });
            my $item_paintGroup = $item_paintAll->get_group();
            $subMenu_painter->append($item_paintAll);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'paint_all', $item_paintAll);

            my $item_paintNew = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_paintGroup,
                'Paint _only new rooms',
            );
            if (! $self->worldModelObj->paintAllRoomsFlag) {

                $item_paintNew->set_active(TRUE);
            }
            $item_paintNew->signal_connect('toggled' => sub {

                if ($item_paintNew->get_active) {

                    $self->worldModelObj->set_paintAllRoomsFlag(FALSE);

                    # Set the equivalent toolbar button
                    if ($self->ivExists('menuToolItemHash', 'icon_paint_new')) {

                        $self->ivShow('menuToolItemHash', 'icon_paint_new')->set_active(TRUE);
                    }
                }
            });
            $subMenu_painter->append($item_paintNew);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'paint_new', $item_paintNew);

            $subMenu_painter->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_paintNormal = Gtk3::RadioMenuItem->new_with_mnemonic(
                undef,
                'Paint _normal rooms',
            );
            $item_paintNormal->signal_connect('toggled' => sub {

                if ($item_paintNormal->get_active) {

                    $self->worldModelObj->painterObj->ivPoke('wildMode', 'normal');

                    # Set the equivalent toolbar button
                    if ($self->ivExists('menuToolItemHash', 'icon_paint_normal')) {

                        $self->ivShow('menuToolItemHash', 'icon_paint_normal')->set_active(TRUE);
                    }
                }
            });
            my $item_paintGroup2 = $item_paintNormal->get_group();
            $subMenu_painter->append($item_paintNormal);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'paint_normal', $item_paintNormal);

            my $item_paintWild = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_paintGroup2,
                'Paint _wilderness rooms',
            );
            if ($self->worldModelObj->painterObj->wildMode eq 'wild') {

                $item_paintWild->set_active(TRUE);
            }
            $item_paintWild->signal_connect('toggled' => sub {

                if ($item_paintWild->get_active) {

                    $self->worldModelObj->painterObj->ivPoke('wildMode', 'wild');

                    # Set the equivalent toolbar button
                    if ($self->ivExists('menuToolItemHash', 'icon_paint_wild')) {

                        $self->ivShow('menuToolItemHash', 'icon_paint_wild')->set_active(TRUE);
                    }
                }
            });
            $subMenu_painter->append($item_paintWild);
            # (Requires $self->session->currentWorld->basicMappingFlag to be FALSE)
            $self->ivAdd('menuToolItemHash', 'paint_wild', $item_paintWild);

            my $item_paintBorder = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_paintGroup2,
                'Paint wilderness _border rooms',
            );
            if ($self->worldModelObj->painterObj->wildMode eq 'border') {

                $item_paintBorder->set_active(TRUE);
            }
            $item_paintBorder->signal_connect('toggled' => sub {

                if ($item_paintBorder->get_active) {

                    $self->worldModelObj->painterObj->ivPoke('wildMode', 'border');

                    # Set the equivalent toolbar button
                    if ($self->ivExists('menuToolItemHash', 'icon_paint_border')) {

                        $self->ivShow('menuToolItemHash', 'icon_paint_border')->set_active(TRUE);
                    }
                }
            });
            $subMenu_painter->append($item_paintBorder);
            # (Requires $self->session->currentWorld->basicMappingFlag to be FALSE)
            $self->ivAdd('menuToolItemHash', 'paint_border', $item_paintBorder);

            $subMenu_painter->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_repaintCurrentRoom = Gtk3::MenuItem->new('Repaint _current room');
            $item_repaintCurrentRoom->signal_connect('activate' => sub {

                if ($self->mapObj->currentRoom) {

                    # Repaint the current room. The TRUE argument instructs the function to tell
                    #   the world model to redraw the room in every Automapper window
                    $self->paintRoom($self->mapObj->currentRoom, TRUE);
                }
            });
            $subMenu_painter->append($item_repaintCurrentRoom);
            # (Requires $self->currentRegionmap and $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'repaint_current', $item_repaintCurrentRoom);

            my $item_repaintSelectedRooms = Gtk3::MenuItem->new('Repaint _selected rooms');
            $item_repaintSelectedRooms->signal_connect('activate' => sub {

                $self->repaintSelectedRoomsCallback();
            });
            $subMenu_painter->append($item_repaintSelectedRooms);
            # (Requires $self->currentRegionmap and either $self->selectedRoom or
            #   $self->selectedRoomHash)
            $self->ivAdd('menuToolItemHash', 'repaint_selected', $item_repaintSelectedRooms);

            $subMenu_painter->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_editPainter = Gtk3::ImageMenuItem->new('_Edit painter...');
            my $img_editPainter = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
            $item_editPainter->set_image($img_editPainter);
            $item_editPainter->signal_connect('activate' => sub {

                # Open an 'edit' window for the painter object
                $self->createFreeWin(
                    'Games::Axmud::EditWin::Painter',
                    $self,
                    $self->session,
                    'Edit world model painter',
                    $self->worldModelObj->painterObj,
                    FALSE,          # Not temporary
                );
            });
            $subMenu_painter->append($item_editPainter);

            $subMenu_painter->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_resetPainter = Gtk3::MenuItem->new('_Reset painter');
            $item_resetPainter->signal_connect('activate' => sub {

                $self->worldModelObj->resetPainter($self->session);

                $self->showMsgDialogue(
                    'Painter',
                    'info',
                    'The painter object has been reset',
                    'ok',
                );
            });
            $subMenu_painter->append($item_resetPainter);

        my $item_painter = Gtk3::ImageMenuItem->new('_Painter');
        my $img_painter = Gtk3::Image->new_from_stock('gtk-select-color', 'menu');
        $item_painter->set_image($img_painter);
        $item_painter->set_submenu($subMenu_painter);
        $column_mode->append($item_painter);

        $column_mode->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Auto-compare' submenu
            my $subMenu_autoCompare = Gtk3::Menu->new();

            my $item_compareDefault = Gtk3::RadioMenuItem->new_with_mnemonic(
                undef,
                '_Don\'t auto-compare current room',
            );
            $item_compareDefault->signal_connect('toggled' => sub {

                if ($item_compareDefault->get_active) {

                    $self->worldModelObj->setAutoCompareMode('default');
                }
            });
            my $item_compareGroup = $item_compareDefault->get_group();
            $subMenu_autoCompare->append($item_compareDefault);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_compare_default', $item_compareDefault);

            my $item_compareNew = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_compareGroup,
                'Auto-compare _new rooms',
            );
            if ($self->worldModelObj->autoCompareMode eq 'new') {

                $item_compareNew->set_active(TRUE);
            }
            $item_compareNew->signal_connect('toggled' => sub {

                if ($item_compareNew->get_active) {

                    $self->worldModelObj->setAutoCompareMode('new');
                }
            });
            $subMenu_autoCompare->append($item_compareNew);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_compare_new', $item_compareNew);

            my $item_compareCurrent = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_compareGroup,
                'Auto-compare the _current room',
            );
            if ($self->worldModelObj->autoCompareMode eq 'current') {

                $item_compareCurrent->set_active(TRUE);
            }
            $item_compareCurrent->signal_connect('toggled' => sub {

                if ($item_compareCurrent->get_active) {

                    $self->worldModelObj->setAutoCompareMode('current');
                }
            });
            $subMenu_autoCompare->append($item_compareCurrent);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_compare_current', $item_compareCurrent);

            $subMenu_autoCompare->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_compareRegion = Gtk3::RadioMenuItem->new_with_mnemonic(
                undef,
                'Compare with rooms in _same region',
            );
            $item_compareRegion->signal_connect('toggled' => sub {

                if ($item_compareRegion->get_active) {

                    $self->worldModelObj->toggleAutoCompareAllFlag(FALSE);
                }
            });
            my $item_compareRegionGroup = $item_compareRegion->get_group();
            $subMenu_autoCompare->append($item_compareRegion);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_compare_region', $item_compareRegion);

            my $item_compareWhole = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_compareRegionGroup,
                'Compare with rooms in _whole world',
            );
            if ($self->worldModelObj->autoCompareAllFlag) {

                $item_compareWhole->set_active(TRUE);
            }
            $item_compareWhole->signal_connect('toggled' => sub {

                if ($item_compareWhole->get_active) {

                    $self->worldModelObj->toggleAutoCompareAllFlag(TRUE);
                }
            });
            $subMenu_autoCompare->append($item_compareWhole);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_compare_model', $item_compareWhole);

            $subMenu_autoCompare->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_compareMax = Gtk3::MenuItem->new('Set _limit on room comparisons...');
            $item_compareMax->signal_connect('activate' => sub {

                $self->autoCompareMaxCallback();
            });
            $subMenu_autoCompare->append($item_compareMax);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_compare_max', $item_compareMax);

        my $item_autoCompare = Gtk3::MenuItem->new('_Auto-compare');
        $item_autoCompare->set_submenu($subMenu_autoCompare);
        $column_mode->append($item_autoCompare);

            # 'Auto-rescue mode' submenu
            my $subMenu_autoRescue = Gtk3::Menu->new();

            my $item_autoRescueEnable = Gtk3::CheckMenuItem->new('_Enable auto-rescue mode');
            $item_autoRescueEnable->set_active($self->worldModelObj->autoRescueFlag);
            $item_autoRescueEnable->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autoRescueFlag',
                        $item_autoRescueEnable->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'auto_rescue',
                    );
                }
            });
            $subMenu_autoRescue->append($item_autoRescueEnable);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_rescue', $item_autoRescueEnable);

            $subMenu_autoRescue->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_autoRescueFirst = Gtk3::CheckMenuItem->new(
                '_Merge at first matching room',
            );
            $item_autoRescueFirst->set_active($self->worldModelObj->autoRescueFirstFlag);
            $item_autoRescueFirst->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autoRescueFirstFlag',
                        $item_autoRescueFirst->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'auto_rescue_prompt',
                    );
                }
            });
            $subMenu_autoRescue->append($item_autoRescueFirst);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_rescue_first', $item_autoRescueFirst);

            my $item_autoRescuePrompt = Gtk3::CheckMenuItem->new('_Prompt before merging');
            $item_autoRescuePrompt->set_active($self->worldModelObj->autoRescuePromptFlag);
            $item_autoRescuePrompt->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autoRescuePromptFlag',
                        $item_autoRescuePrompt->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'auto_rescue_prompt',
                    );
                }
            });
            $subMenu_autoRescue->append($item_autoRescuePrompt);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_rescue_prompt', $item_autoRescuePrompt);

            $subMenu_autoRescue->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_autoRescueNoMove = Gtk3::CheckMenuItem->new('_Don\'t move non-matching rooms');
            $item_autoRescueNoMove->set_active($self->worldModelObj->autoRescueNoMoveFlag);
            $item_autoRescueNoMove->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autoRescueNoMoveFlag',
                        $item_autoRescueNoMove->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'auto_rescue_no_move',
                    );
                }
            });
            $subMenu_autoRescue->append($item_autoRescueNoMove);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_rescue_no_move', $item_autoRescueNoMove);

            my $item_autoRescueVisits = Gtk3::CheckMenuItem->new(
                '_Only update visits in merged rooms',
            );
            $item_autoRescueVisits->set_active($self->worldModelObj->autoRescueVisitsFlag);
            $item_autoRescueVisits->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autoRescueVisitsFlag',
                        $item_autoRescueVisits->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'auto_rescue_visits',
                    );
                }
            });
            $subMenu_autoRescue->append($item_autoRescueVisits);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_rescue_visits', $item_autoRescueVisits);

            my $item_autoRescueForce = Gtk3::CheckMenuItem->new(
                '_Temporarily switch to \'update\' mode',
            );
            $item_autoRescueForce->set_active($self->worldModelObj->autoRescueForceFlag);
            $item_autoRescueForce->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autoRescueForceFlag',
                        $item_autoRescueForce->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'auto_rescue_force',
                    );
                }
            });
            $subMenu_autoRescue->append($item_autoRescueForce);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_rescue_force', $item_autoRescueForce);

        my $item_autoRescue = Gtk3::MenuItem->new('Auto-r_escue mode');
        $item_autoRescue->set_submenu($subMenu_autoRescue);
        $column_mode->append($item_autoRescue);

            # 'Auto-slide mode' submenu
            my $subMenu_autoSlide = Gtk3::Menu->new();

            my $item_slideMode = Gtk3::RadioMenuItem->new_with_mnemonic(
                undef,
                '_Don\'t auto-slide new rooms',
            );
            $item_slideMode->signal_connect('toggled' => sub {

                if ($item_slideMode->get_active) {

                    $self->worldModelObj->setAutoSlideMode('default');
                }
            });
            my $item_slideGroup = $item_slideMode->get_group();
            $subMenu_autoSlide->append($item_slideMode);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_default', $item_slideMode);

            my $item_slideOrigPull = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_slideGroup,
                'Slide original room _backwards',
            );
            if ($self->worldModelObj->autoSlideMode eq 'orig_pull') {

                $item_slideOrigPull->set_active(TRUE);
            }
            $item_slideOrigPull->signal_connect('toggled' => sub {

                if ($item_slideOrigPull->get_active) {

                    $self->worldModelObj->setAutoSlideMode('orig_pull');
                }
            });
            $subMenu_autoSlide->append($item_slideOrigPull);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_orig_pull', $item_slideOrigPull);

            my $item_slideOrigPush = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_slideGroup,
                'Slide original room _forwards',
            );
            if ($self->worldModelObj->autoSlideMode eq 'orig_push') {

                $item_slideOrigPush->set_active(TRUE);
            }
            $item_slideOrigPush->signal_connect('toggled' => sub {

                if ($item_slideOrigPush->get_active) {

                    $self->worldModelObj->setAutoSlideMode('orig_push');
                }
            });
            $subMenu_autoSlide->append($item_slideOrigPush);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_orig_pull', $item_slideOrigPush);

            my $item_slideOtherPull = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_slideGroup,
                'Slide blocking room b_ackwards',
            );
            if ($self->worldModelObj->autoSlideMode eq 'other_pull') {

                $item_slideOtherPull->set_active(TRUE);
            }
            $item_slideOtherPull->signal_connect('toggled' => sub {

                if ($item_slideOtherPull->get_active) {

                    $self->worldModelObj->setAutoSlideMode('other_pull');
                }
            });
            $subMenu_autoSlide->append($item_slideOtherPull);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_orig_pull', $item_slideOtherPull);

            my $item_slideOtherPush = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_slideGroup,
                'Slide blocking room f_orwards',
            );
            if ($self->worldModelObj->autoSlideMode eq 'other_push') {

                $item_slideOtherPush->set_active(TRUE);
            }
            $item_slideOtherPush->signal_connect('toggled' => sub {

                if ($item_slideOtherPush->get_active) {

                    $self->worldModelObj->setAutoSlideMode('other_push');
                }
            });
            $subMenu_autoSlide->append($item_slideOtherPush);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_orig_pull', $item_slideOtherPush);

            my $item_slideDestPull = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_slideGroup,
                'Slide new room ba_ckwards',
            );
            if ($self->worldModelObj->autoSlideMode eq 'dest_pull') {

                $item_slideDestPull->set_active(TRUE);
            }
            $item_slideDestPull->signal_connect('toggled' => sub {

                if ($item_slideDestPull->get_active) {

                    $self->worldModelObj->setAutoSlideMode('dest_pull');
                }
            });
            $subMenu_autoSlide->append($item_slideDestPull);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_orig_pull', $item_slideDestPull);

            my $item_slideDestPush = Gtk3::RadioMenuItem->new_with_mnemonic(
                $item_slideGroup,
                'Slide new room fo_rwards',
            );
            if ($self->worldModelObj->autoSlideMode eq 'dest_push') {

                $item_slideDestPush->set_active(TRUE);
            }
            $item_slideDestPush->signal_connect('toggled' => sub {

                if ($item_slideDestPush->get_active) {

                    $self->worldModelObj->setAutoSlideMode('dest_push');
                }
            });
            $subMenu_autoSlide->append($item_slideDestPush);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_orig_pull', $item_slideDestPush);

            $subMenu_autoSlide->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_slideMax = Gtk3::MenuItem->new('Set _limit on slide distance...');
            $item_slideMax->signal_connect('activate' => sub {

                $self->autoSlideMaxCallback();
            });
            $subMenu_autoSlide->append($item_slideMax);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'slide_max', $item_slideMax);

        my $item_autoSlide = Gtk3::MenuItem->new('Auto-s_lide mode');
        $item_autoSlide->set_submenu($subMenu_autoSlide);
        $column_mode->append($item_autoSlide);

        $column_mode->append(Gtk3::SeparatorMenuItem->new());   # Separator

            # 'Start-up flags' submenu
            my $subMenu_startUpFlags = Gtk3::Menu->new();

            my $item_autoOpenWindow = Gtk3::CheckMenuItem->new('Open _automapper on startup');
            $item_autoOpenWindow->set_active($self->worldModelObj->autoOpenWinFlag);
            $item_autoOpenWindow->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autoOpenWinFlag',
                        $item_autoOpenWindow->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'auto_open_win',
                    );
                }
            });
            $subMenu_startUpFlags->append($item_autoOpenWindow);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'auto_open_win', $item_autoOpenWindow);

            my $item_pseudoWin = Gtk3::CheckMenuItem->new('Open as _pseudo-window');
            $item_pseudoWin->set_active($self->worldModelObj->pseudoWinFlag);
            $item_pseudoWin->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'pseudoWinFlag',
                        $item_pseudoWin->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'pseudo_win',
                    );
                }
            });
            $subMenu_startUpFlags->append($item_pseudoWin);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'pseudo_win', $item_pseudoWin);

            my $item_allowTrackAlone = Gtk3::CheckMenuItem->new('_Follow character after closing');
            $item_allowTrackAlone->set_active($self->worldModelObj->allowTrackAloneFlag);
            $item_allowTrackAlone->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'allowTrackAloneFlag',
                        $item_allowTrackAlone->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'keep_following',
                    );
                }
            });
            $subMenu_startUpFlags->append($item_allowTrackAlone);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'keep_following', $item_allowTrackAlone);

        my $item_startUpFlags = Gtk3::MenuItem->new('S_tart-up flags');
        $item_startUpFlags->set_submenu($subMenu_startUpFlags);
        $column_mode->append($item_startUpFlags);

            # 'Drawing flags' submenu
            my $subMenu_drawingFlags = Gtk3::Menu->new();

            my $item_roomTagsInCaps = Gtk3::CheckMenuItem->new('_Capitalise room tags');
            $item_roomTagsInCaps->set_active($self->worldModelObj->capitalisedRoomTagFlag);
            $item_roomTagsInCaps->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'capitalisedRoomTagFlag',
                        $item_roomTagsInCaps->get_active(),
                        TRUE,      # Do call $self->redrawRegions
                        'room_tags_capitalised',
                    );
                }
            });
            $subMenu_drawingFlags->append($item_roomTagsInCaps);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'room_tags_capitalised', $item_roomTagsInCaps);

            my $item_drawBentExits = Gtk3::CheckMenuItem->new('Draw _bent broken exits');
            $item_drawBentExits->set_active($self->worldModelObj->drawBentExitsFlag);
            $item_drawBentExits->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'drawBentExitsFlag',
                        $item_drawBentExits->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'draw_bent_exits',
                    );
                }
            });
            $subMenu_drawingFlags->append($item_drawBentExits);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_bent_exits', $item_drawBentExits);

            my $item_drawRoomEcho = Gtk3::CheckMenuItem->new('Draw _room echos');
            $item_drawRoomEcho->set_active($self->worldModelObj->drawRoomEchoFlag);
            $item_drawRoomEcho->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'drawRoomEchoFlag',
                        $item_drawRoomEcho->get_active(),
                        TRUE,      # Do call $self->redrawRegions
                        'draw_room_echo',
                    );
                }
            });
            $subMenu_drawingFlags->append($item_drawRoomEcho);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_room_echo', $item_drawRoomEcho);

            my $item_showTooltips = Gtk3::CheckMenuItem->new('Show _tooltips');
            $item_showTooltips->set_active($self->worldModelObj->showTooltipsFlag);
            $item_showTooltips->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleShowTooltipsFlag(
                    $item_showTooltips->get_active(),
                );
            });
            $subMenu_drawingFlags->append($item_showTooltips);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'show_tooltips', $item_showTooltips);

            my $item_showNotes = Gtk3::CheckMenuItem->new('Show room _notes in tooltips');
            $item_showNotes->set_active($self->worldModelObj->showNotesFlag);
            $item_showNotes->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'showNotesFlag',
                        $item_showNotes->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'show_notes',
                    );
                }
            });
            $subMenu_drawingFlags->append($item_showNotes);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'show_notes', $item_showNotes);

        my $item_drawingFlags = Gtk3::MenuItem->new('Draw_ing flags');
        $item_drawingFlags->set_submenu($subMenu_drawingFlags);
        $column_mode->append($item_drawingFlags);

            # 'Movement flags' submenu
            my $subMenu_moves = Gtk3::Menu->new();

            my $item_allowAssisted = Gtk3::CheckMenuItem->new('_Allow assisted moves');
            $item_allowAssisted->set_active($self->worldModelObj->assistedMovesFlag);
            $item_allowAssisted->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'assistedMovesFlag',
                        $item_allowAssisted->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'allow_assisted_moves',
                    );

                    # The menu items below which set ->protectedMovesFlag and
                    #   ->superProtectedMovesFlag are desensitised if ->assistedMovesFlag is FALSE
                    # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
                    $self->restrictWidgets();
                }
            });
            $subMenu_moves->append($item_allowAssisted);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_assisted_moves', $item_allowAssisted);

            $subMenu_moves->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_assistedBreak = Gtk3::CheckMenuItem->new('_Break doors before move');
            $item_assistedBreak->set_active($self->worldModelObj->assistedBreakFlag);
            $item_assistedBreak->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'assistedBreakFlag',
                        $item_assistedBreak->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'break_before_move',
                    );
                }
            });
            $subMenu_moves->append($item_assistedBreak);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'break_before_move', $item_assistedBreak);

            my $item_assistedPick = Gtk3::CheckMenuItem->new('_Pick doors before move');
            $item_assistedPick->set_active($self->worldModelObj->assistedPickFlag);
            $item_assistedPick->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'assistedPickFlag',
                        $item_assistedPick->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'pick_before_move',
                    );
                }
            });
            $subMenu_moves->append($item_assistedPick);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'pick_before_move', $item_assistedPick);

            my $item_assistedUnlock = Gtk3::CheckMenuItem->new('_Unlock doors before move');
            $item_assistedUnlock->set_active($self->worldModelObj->assistedUnlockFlag);
            $item_assistedUnlock->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'assistedUnlockFlag',
                        $item_assistedUnlock->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'unlock_before_move',
                    );
                }
            });
            $subMenu_moves->append($item_assistedUnlock);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'unlock_before_move', $item_assistedUnlock);

            my $item_assistedOpen = Gtk3::CheckMenuItem->new('_Open doors before move');
            $item_assistedOpen->set_active($self->worldModelObj->assistedOpenFlag);
            $item_assistedOpen->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'assistedOpenFlag',
                        $item_assistedOpen->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'open_before_move',
                    );
                }
            });
            $subMenu_moves->append($item_assistedOpen);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'open_before_move', $item_assistedOpen);

            my $item_assistedClose = Gtk3::CheckMenuItem->new('_Close doors after move');
            $item_assistedClose->set_active($self->worldModelObj->assistedCloseFlag);
            $item_assistedClose->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'assistedCloseFlag',
                        $item_assistedClose->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'close_after_move',
                    );
                }
            });
            $subMenu_moves->append($item_assistedClose);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'close_after_move', $item_assistedClose);

            my $item_assistedLock = Gtk3::CheckMenuItem->new('_Lock doors after move');
            $item_assistedLock->set_active($self->worldModelObj->assistedLockFlag);
            $item_assistedLock->signal_connect('toggled' => sub {

                if (! $self->assistedLockFlag) {

                    $self->worldModelObj->toggleFlag(
                        'assistedLockFlag',
                        $item_assistedLock->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'lock_after_move',
                    );
                }
            });
            $subMenu_moves->append($item_assistedLock);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'lock_after_move', $item_assistedLock);

            $subMenu_moves->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_allowProtected = Gtk3::CheckMenuItem->new('Allow p_rotected moves');
            $item_allowProtected->set_active($self->worldModelObj->protectedMovesFlag);
            $item_allowProtected->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'protectedMovesFlag',
                        $item_allowProtected->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'allow_protected_moves',
                    );

                    # The menu item below which sets ->crafyMovesFlag is desensitised if
                    #   ->assistedMovesFlag is false
                    # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
                    $self->restrictWidgets();
                }
            });
            $subMenu_moves->append($item_allowProtected);
            # (Requires $self->worldModelObj->assistedMovesFlag)
            $self->ivAdd('menuToolItemHash', 'allow_protected_moves', $item_allowProtected);

            my $item_allowSuper = Gtk3::CheckMenuItem->new('Ca_ncel commands when overruled');
            $item_allowSuper->set_active($self->worldModelObj->superProtectedMovesFlag);
            $item_allowSuper->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'superProtectedMovesFlag',
                        $item_allowSuper->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'allow_super_protected_moves',
                    );
                }
            });
            $subMenu_moves->append($item_allowSuper);
            # (Requires $self->worldModelObj->assistedMovesFlag)
            $self->ivAdd('menuToolItemHash', 'allow_super_protected_moves', $item_allowSuper);

            $subMenu_moves->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_allowCrafty = Gtk3::CheckMenuItem->new('Allow crafty _moves');
            $item_allowCrafty->set_active($self->worldModelObj->craftyMovesFlag);
            $item_allowCrafty->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'craftyMovesFlag',
                        $item_allowCrafty->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'allow_crafty_moves',
                    );
                }
            });
            $subMenu_moves->append($item_allowCrafty);
            # (Requires $self->worldModelObj->protectedMovesFlag set to be FALSE)
            $self->ivAdd('menuToolItemHash', 'allow_crafty_moves', $item_allowCrafty);

        my $item_moves = Gtk3::MenuItem->new('Mo_vement flags');
        $item_moves->set_submenu($subMenu_moves);
        $column_mode->append($item_moves);

            # 'Other flags' submenu
            my $subMenu_otherFlags = Gtk3::Menu->new();

            my $item_allowModelScripts = Gtk3::CheckMenuItem->new('_Allow model-wide scripts');
            $item_allowModelScripts->set_active($self->worldModelObj->allowModelScriptFlag);
            $item_allowModelScripts->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'allowModelScriptFlag',
                        $item_allowModelScripts->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'allow_model_scripts',
                    );
                }
            });
            $subMenu_otherFlags->append($item_allowModelScripts);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_model_scripts', $item_allowModelScripts);

            my $item_allowRoomScripts = Gtk3::CheckMenuItem->new(
                'Allow ' . $axmud::BASIC_NAME . ' _scripts',
            );
            $item_allowRoomScripts->set_active($self->worldModelObj->allowRoomScriptFlag);
            $item_allowRoomScripts->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'allowRoomScriptFlag',
                        $item_allowRoomScripts->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'allow_room_scripts',
                    );
                }
            });
            $subMenu_otherFlags->append($item_allowRoomScripts);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_room_scripts', $item_allowRoomScripts);

            my $item_countVisits = Gtk3::CheckMenuItem->new('_Count character visits');
            $item_countVisits->set_active($self->worldModelObj->countVisitsFlag);
            $item_countVisits->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'countVisitsFlag',
                        $item_countVisits->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'count_char_visits',
                    );
                }
            });
            $subMenu_otherFlags->append($item_countVisits);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'count_char_visits', $item_countVisits);

            my $item_disableUpdate = Gtk3::CheckMenuItem->new('_Disable update mode');
            $item_disableUpdate->set_active($self->worldModelObj->disableUpdateModeFlag);
            $item_disableUpdate->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleDisableUpdateModeFlag(
                        $item_disableUpdate->get_active(),
                    );
                }
            });
            $subMenu_otherFlags->append($item_disableUpdate);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'disable_update_mode', $item_disableUpdate);

            my $item_explainGetLost = Gtk3::CheckMenuItem->new('_Explain when getting lost');
            $item_explainGetLost->set_active($self->worldModelObj->explainGetLostFlag);
            $item_explainGetLost->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'explainGetLostFlag',
                        $item_explainGetLost->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'explain_get_lost',
                    );
                }
            });
            $subMenu_otherFlags->append($item_explainGetLost);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'explain_get_lost', $item_explainGetLost);

            my $item_followAnchor = Gtk3::CheckMenuItem->new('New exits for _follow anchors');
            $item_followAnchor->set_active($self->worldModelObj->followAnchorFlag);
            $item_followAnchor->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'followAnchorFlag',
                        $item_followAnchor->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'follow_anchor',
                    );
                }
            });
            $subMenu_otherFlags->append($item_followAnchor);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'follow_anchor', $item_followAnchor);

            my $item_allowCtrlCopy = Gtk3::CheckMenuItem->new('_Move rooms to click with CTRL+C');
            $item_allowCtrlCopy->set_active($self->worldModelObj->allowCtrlCopyFlag);
            $item_allowCtrlCopy->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'allowCtrlCopyFlag',
                        $item_allowCtrlCopy->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'allow_ctrl_copy',
                    );
                }
            });
            $subMenu_otherFlags->append($item_allowCtrlCopy);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_ctrl_copy', $item_allowCtrlCopy);

            my $item_showAllPrimary = Gtk3::CheckMenuItem->new('S_how all directions in dialogues');
            $item_showAllPrimary->set_active($self->worldModelObj->showAllPrimaryFlag);
            $item_showAllPrimary->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'showAllPrimaryFlag',
                        $item_showAllPrimary->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'show_all_primary',
                    );
                }
            });
            $subMenu_otherFlags->append($item_showAllPrimary);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'show_all_primary', $item_showAllPrimary);

        my $item_otherFlags = Gtk3::MenuItem->new('_Other flags');
        $item_otherFlags->set_submenu($subMenu_otherFlags);
        $column_mode->append($item_otherFlags);

        # Setup complete
        return $column_mode;
    }

    sub enableRegionsColumn {

        # Called by $self->enableMenu
        # Sets up the 'Regions' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableRegionsColumn', @_);
        }

        # Set up column
        my $column_regions = Gtk3::Menu->new();
        if (! $column_regions) {

            return undef;
        }

        my $item_newRegion = Gtk3::ImageMenuItem->new('_New region...');
        my $img_newRegion = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_newRegion->set_image($img_newRegion);
        $item_newRegion->signal_connect('activate' => sub {

            $self->newRegionCallback(FALSE);
        });
        $column_regions->append($item_newRegion);

        my $item_newTempRegion = Gtk3::ImageMenuItem->new('New _temporary region...');
        my $img_newTempRegion = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_newTempRegion->set_image($img_newTempRegion);
        $item_newTempRegion->signal_connect('activate' => sub {

            $self->newRegionCallback(TRUE);
        });
        $column_regions->append($item_newTempRegion);

        $column_regions->append(Gtk3::SeparatorMenuItem->new());    # Separator

        my $item_editRegion = Gtk3::ImageMenuItem->new('_Edit region...');
        my $img_editRegion = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editRegion->set_image($img_editRegion);
        $item_editRegion->signal_connect('activate' => sub {

            $self->editRegionCallback();
        });
        $column_regions->append($item_editRegion);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'edit_region', $item_editRegion);

        my $item_editRegionmap = Gtk3::ImageMenuItem->new('Edit _regionmap...');
        my $img_editRegionmap = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editRegionmap->set_image($img_editRegionmap);
        $item_editRegionmap->signal_connect('activate' => sub {

            # Open an 'edit' window for the regionmap
            $self->createFreeWin(
                'Games::Axmud::EditWin::Regionmap',
                $self,
                $self->session,
                'Edit \'' . $self->currentRegionmap->name . '\' regionmap',
                $self->currentRegionmap,
                FALSE,          # Not temporary
            );
        });
        $column_regions->append($item_editRegionmap);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'edit_regionmap', $item_editRegionmap);

        $column_regions->append(Gtk3::SeparatorMenuItem->new());    # Separator

            # 'Region list' submenu
            my $subMenu_regionsTree = Gtk3::Menu->new();

            my $item_resetList = Gtk3::MenuItem->new('_Reset region list');
            $item_resetList->signal_connect('activate' => sub {

                $self->worldModelObj->resetRegionList();
            });
            $subMenu_regionsTree->append($item_resetList);

            my $item_reverseList = Gtk3::MenuItem->new('Re_verse region list');
            $item_reverseList->signal_connect('activate' => sub {

                $self->worldModelObj->reverseRegionList();
            });
            $subMenu_regionsTree->append($item_reverseList);

            $subMenu_regionsTree->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_moveCurrentRegion = Gtk3::MenuItem->new('_Move current region to top');
            $item_moveCurrentRegion->signal_connect('activate' => sub {

                $self->worldModelObj->moveRegionToTop($self->currentRegionmap);
            });
            $subMenu_regionsTree->append($item_moveCurrentRegion);
            # (Requires $self->currentRegionmap for a region that doesn't have a parent region)
            $self->ivAdd('menuToolItemHash', 'move_region_top', $item_moveCurrentRegion);

            $subMenu_regionsTree->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_identifyRegion = Gtk3::MenuItem->new('_Identify highlighted region');
            $item_identifyRegion->signal_connect('activate' => sub {

                $self->identifyRegionCallback();
            });
            $subMenu_regionsTree->append($item_identifyRegion);
            # (Requires $self->treeViewSelectedLine)
            $self->ivAdd('menuToolItemHash', 'identify_region', $item_identifyRegion);

        my $item_regionsTree = Gtk3::MenuItem->new('Region _list');
        $item_regionsTree->set_submenu($subMenu_regionsTree);
        $column_regions->append($item_regionsTree);

            # 'Current region' submenu
            my $subMenu_currentRegion = Gtk3::Menu->new();

            my $item_renameRegion = Gtk3::MenuItem->new('_Rename region...');
            $item_renameRegion->signal_connect('activate' => sub {

                $self->renameRegionCallback();
            });
            $subMenu_currentRegion->append($item_renameRegion);

            my $item_changeParent = Gtk3::MenuItem->new('_Set parent region...');
            $item_changeParent->signal_connect('activate' => sub {

                $self->changeRegionParentCallback();
            });
            $subMenu_currentRegion->append($item_changeParent);

            $subMenu_currentRegion->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_regionFinished = Gtk3::MenuItem->new('Set f_inished region');
            $item_regionFinished->signal_connect('activate' => sub {

                $self->regionFinishedCallback();
            });
            $subMenu_currentRegion->append($item_regionFinished);

            $subMenu_currentRegion->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_convertRegionExit = Gtk3::MenuItem->new('_Convert all region exits');
            $item_convertRegionExit->signal_connect('activate' => sub {

                $self->convertRegionExitCallback(TRUE);
            });
            $subMenu_currentRegion->append($item_convertRegionExit);

            my $item_deconvertRegionExit = Gtk3::MenuItem->new(
                '_Deconvert all super-region exits',
            );
            $item_deconvertRegionExit->signal_connect('activate' => sub {

                $self->convertRegionExitCallback(FALSE);
            });
            $subMenu_currentRegion->append($item_deconvertRegionExit);

            $subMenu_currentRegion->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_resetObjectCounts = Gtk3::MenuItem->new('Reset _object counts');
            $item_resetObjectCounts->signal_connect('activate' => sub {

                 # Empty the hashes which store temporary object counts and redraw the region
                 $self->worldModelObj->resetRegionCounts($self->currentRegionmap);
            });
            $subMenu_currentRegion->append($item_resetObjectCounts);

            my $item_removeRoomFlags = Gtk3::MenuItem->new('Remove room _flags...');
            $item_removeRoomFlags->signal_connect('activate' => sub {

                $self->removeRoomFlagsCallback();
            });
            $subMenu_currentRegion->append($item_removeRoomFlags);

        my $item_currentRegion = Gtk3::MenuItem->new('C_urrent region');
        $item_currentRegion->set_submenu($subMenu_currentRegion);
        $column_regions->append($item_currentRegion);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'current_region', $item_currentRegion);

            # 'Pre-drawn regions' submenu
            my $subMenu_preDrawRegion = Gtk3::Menu->new();

            my $item_allowPreDraw = Gtk3::CheckMenuItem->new('_Allow pre-drawing of maps');
            $item_allowPreDraw->set_active($self->worldModelObj->preDrawAllowFlag);
            $item_allowPreDraw->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleFlag(
                    'preDrawAllowFlag',
                    $item_allowPreDraw->get_active(),
                    FALSE,      # Don't call $self->redrawRegions
                    'allow_pre_draw',
                );
            });
            $subMenu_preDrawRegion->append($item_allowPreDraw);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_pre_draw', $item_allowPreDraw);

            my $item_setPreDrawSize = Gtk3::MenuItem->new('_Set minimum region size');
            $item_setPreDrawSize->signal_connect('activate' => sub {

                $self->preDrawSizeCallback();
            });
            $subMenu_preDrawRegion->append($item_setPreDrawSize);

            my $item_setRetainSize = Gtk3::MenuItem->new('Set minimum retention size');
            $item_setRetainSize->signal_connect('activate' => sub {

                $self->preDrawRetainCallback();
            });
            $subMenu_preDrawRegion->append($item_setRetainSize);

            my $item_setPreDrawSpeed = Gtk3::MenuItem->new('Set pre-draw speed');
            $item_setPreDrawSpeed->signal_connect('activate' => sub {

                $self->preDrawSpeedCallback();
            });
            $subMenu_preDrawRegion->append($item_setPreDrawSpeed);

            $subMenu_preDrawRegion->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_redrawRegion = Gtk3::MenuItem->new('Re_draw this region');
            $item_redrawRegion->signal_connect('activate' => sub {

                $self->redrawRegions($self->currentRegionmap, TRUE);
            });
            $subMenu_preDrawRegion->append($item_redrawRegion);
            # (Requires $self->currentRegionmap)
            $self->ivAdd('menuToolItemHash', 'redraw_region', $item_redrawRegion);

            my $item_redrawAllRegions = Gtk3::MenuItem->new('Redraw _all drawn regions');
            $item_redrawAllRegions->signal_connect('activate' => sub {

                $self->redrawRegionsCallback();
            });
            $subMenu_preDrawRegion->append($item_redrawAllRegions);

        my $item_preDrawRegion = Gtk3::MenuItem->new('_Pre-drawn regions');
        $item_preDrawRegion->set_submenu($subMenu_preDrawRegion);
        $column_regions->append($item_preDrawRegion);

        $column_regions->append(Gtk3::SeparatorMenuItem->new());    # Separator

            # 'Colour schemes' submenu
            my $subMenu_regionScheme = Gtk3::Menu->new();

            my $item_addScheme = Gtk3::MenuItem->new('_Add new colour scheme...');
            $item_addScheme->signal_connect('activate' => sub {

                $self->addRegionSchemeCallback();
            });
            $subMenu_regionScheme->append($item_addScheme);

            my $item_editScheme = Gtk3::MenuItem->new('_Edit colour scheme...');
            $item_editScheme->signal_connect('activate' => sub {

                $self->doRegionSchemeCallback('edit');
            });
            $subMenu_regionScheme->append($item_editScheme);

            my $item_renameScheme = Gtk3::MenuItem->new('_Rename colour scheme...');
            $item_renameScheme->signal_connect('activate' => sub {

                $self->doRegionSchemeCallback('rename');
            });
            $subMenu_regionScheme->append($item_renameScheme);

            my $item_deleteScheme = Gtk3::MenuItem->new('_Delete colour scheme...');
            $item_deleteScheme->signal_connect('activate' => sub {

                $self->doRegionSchemeCallback('delete');
            });
            $subMenu_regionScheme->append($item_deleteScheme);

            $subMenu_regionScheme->append(Gtk3::SeparatorMenuItem->new());  # Separator

                # 'This region' sub-submenu
                my $subSubMenu_thisRegionScheme = Gtk3::Menu->new();

                my $item_attachScheme = Gtk3::MenuItem->new('_Attach colour scheme...');
                $item_attachScheme->signal_connect('activate' => sub {

                    $self->attachRegionSchemeCallback();
                });
                $subSubMenu_thisRegionScheme->append($item_attachScheme);
                # (Requires $self->currentRegionmap and at least one non-default region colour
                #   schemes)
                $self->ivAdd('menuToolItemHash', 'attach_region_scheme', $item_attachScheme);

                my $item_detachScheme = Gtk3::MenuItem->new('_Detach colour scheme');
                $item_detachScheme->signal_connect('activate' => sub {

                    $self->detachRegionSchemeCallback();
                });
                $subSubMenu_thisRegionScheme->append($item_detachScheme);
                # (Requires $self->currentRegionmap with a defined ->regionScheme IV)
                $self->ivAdd('menuToolItemHash', 'detach_region_scheme', $item_detachScheme);

                $subSubMenu_thisRegionScheme->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_editThisScheme = Gtk3::MenuItem->new('_Edit colour scheme...');
                $item_editThisScheme->signal_connect('activate' => sub {

                    $self->doRegionSchemeCallback('edit', $self->currentRegionmap);
                });
                $subSubMenu_thisRegionScheme->append($item_editThisScheme);

            my $item_thisRegionScheme = Gtk3::MenuItem->new('_Current region');
            $item_thisRegionScheme->set_submenu($subSubMenu_thisRegionScheme);
            $subMenu_regionScheme->append($item_thisRegionScheme);
            # (Requires $self->currentRegionmap)
            $self->ivAdd('menuToolItemHash', 'this_region_scheme', $item_thisRegionScheme);

        my $item_colourScheme = Gtk3::MenuItem->new('Colour sc_hemes');
        $item_colourScheme->set_submenu($subMenu_regionScheme);
        $column_regions->append($item_colourScheme);

            # 'Background colours' submenu
            my $subMenu_bgColours = Gtk3::Menu->new();

            my $item_removeBGAll = Gtk3::MenuItem->new('_Remove colour...');
            $item_removeBGAll->signal_connect('activate' => sub {

                $self->removeBGColourCallback();
            });
            $subMenu_bgColours->append($item_removeBGAll);

            my $item_removeBGColour = Gtk3::MenuItem->new('Remove _all colours');
            $item_removeBGColour->signal_connect('activate' => sub {

                $self->removeBGAllCallback();
            });
            $subMenu_bgColours->append($item_removeBGColour);

        my $item_bgColours = Gtk3::MenuItem->new('_Background colours');
        $item_bgColours->set_submenu($subMenu_bgColours);
        $column_regions->append($item_bgColours);
        # (Requires $self->currentRegionmap whose ->gridColourBlockHash and/or ->gridColourObjHash
        #   is not empty)
        $self->ivAdd('menuToolItemHash', 'empty_bg_colours', $item_bgColours);

        $column_regions->append(Gtk3::SeparatorMenuItem->new());    # Separator

            # 'Recalculate paths' submenu
            my $subMenu_recalculatePaths = Gtk3::Menu->new();

            my $item_recalculateInCurrentRegion = Gtk3::MenuItem->new('In _current region');
            $item_recalculateInCurrentRegion->signal_connect('activate' => sub {

                $self->recalculatePathsCallback('current');
            });
            $subMenu_recalculatePaths->append($item_recalculateInCurrentRegion);
            # (Requires $self->currentRegionmap and a non-empty
            #   self->currentRegionmap->gridRoomHash)
            $self->ivAdd(
                'menuToolItemHash',
                'recalculate_in_region',
                $item_recalculateInCurrentRegion,
            );

            my $item_recalculateSelectRegion = Gtk3::MenuItem->new('In _region...');
            $item_recalculateSelectRegion->signal_connect('activate' => sub {

                $self->recalculatePathsCallback('select');
            });
            $subMenu_recalculatePaths->append($item_recalculateSelectRegion);

            my $item_recalculateAllRegions = Gtk3::MenuItem->new('In _all regions');
            $item_recalculateAllRegions->signal_connect('activate' => sub {

                $self->recalculatePathsCallback('all');
            });
            $subMenu_recalculatePaths->append($item_recalculateAllRegions);

            $subMenu_recalculatePaths->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_recalculateFromExit = Gtk3::MenuItem->new('For selected _exit');
            $item_recalculateFromExit->signal_connect('activate' => sub {

                $self->recalculatePathsCallback('exit');
            });
            $subMenu_recalculatePaths->append($item_recalculateFromExit);
            # (Requires $self->currentRegionmap and a $self->selectedExit which is a super-region
            #   exit)
            $self->ivAdd('menuToolItemHash', 'recalculate_from_exit', $item_recalculateFromExit);

        my $item_recalculatePaths = Gtk3::MenuItem->new('Re_calculate region paths');
        $item_recalculatePaths->set_submenu($subMenu_recalculatePaths);
        $column_regions->append($item_recalculatePaths);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'recalculate_paths', $item_recalculatePaths);

            # 'Locate current room' submenu
            my $subMenu_locateCurrentRoom = Gtk3::Menu->new();

            my $item_locateInCurrentRegion = Gtk3::MenuItem->new('In _current region');
            $item_locateInCurrentRegion->signal_connect('activate' => sub {

                $self->locateCurrentRoomCallback('current');
            });
            $subMenu_locateCurrentRoom->append($item_locateInCurrentRegion);
            # (Requires $self->currentRegionmap and a non-empty GA::Obj::Regionmap->gridRoomHash)
            $self->ivAdd('menuToolItemHash', 'locate_room_in_current', $item_locateInCurrentRegion);

            my $item_locateInSelectRegion = Gtk3::MenuItem->new('In _region...');
            $item_locateInSelectRegion->signal_connect('activate' => sub {

                $self->locateCurrentRoomCallback('select');
            });
            $subMenu_locateCurrentRoom->append($item_locateInSelectRegion);

            my $item_locateInAllRegions = Gtk3::MenuItem->new('In _all regions');
            $item_locateInAllRegions->signal_connect('activate' => sub {

                $self->locateCurrentRoomCallback('all');
            });
            $subMenu_locateCurrentRoom->append($item_locateInAllRegions);

        my $item_locateCurrentRoom = Gtk3::ImageMenuItem->new('L_ocate current room');
        my $img_locateCurrentRoom = Gtk3::Image->new_from_stock('gtk-find', 'menu');
        $item_locateCurrentRoom->set_image($img_locateCurrentRoom);
        $item_locateCurrentRoom->set_submenu($subMenu_locateCurrentRoom);
        $column_regions->append($item_locateCurrentRoom);

            # 'Screenshots' submenu
            my $subMenu_screenshots = Gtk3::Menu->new();

            my $item_visibleScreenshot = Gtk3::MenuItem->new('_Visible map');
            $item_visibleScreenshot->signal_connect('activate' => sub {

                $self->regionScreenshotCallback('visible');
            });
            $subMenu_screenshots->append($item_visibleScreenshot);

            my $item_occupiedScreenshot = Gtk3::MenuItem->new('_Occupied portion');
            $item_occupiedScreenshot->signal_connect('activate' => sub {

                $self->regionScreenshotCallback('occupied');
            });
            $subMenu_screenshots->append($item_occupiedScreenshot);

            my $item_wholeScreenshot = Gtk3::MenuItem->new('_Whole region');
            $item_wholeScreenshot->signal_connect('activate' => sub {

                $self->regionScreenshotCallback('whole');
            });
            $subMenu_screenshots->append($item_wholeScreenshot);

        my $item_screenshots = Gtk3::MenuItem->new('Take _screenshot');
        $item_screenshots->set_submenu($subMenu_screenshots);
        $column_regions->append($item_screenshots);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'screenshots', $item_screenshots);

        $column_regions->append(Gtk3::SeparatorMenuItem->new());    # Separator

        my $item_emptyRegion = Gtk3::MenuItem->new('E_mpty region');
        $item_emptyRegion->signal_connect('activate' => sub {

            $self->emptyRegionCallback();
        });
        $column_regions->append($item_emptyRegion);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'empty_region', $item_emptyRegion);

        my $item_deleteRegion = Gtk3::ImageMenuItem->new('_Delete region');
        my $img_deleteRegion = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_deleteRegion->set_image($img_deleteRegion);
        $item_deleteRegion->signal_connect('activate' => sub {

            $self->deleteRegionCallback();
        });
        $column_regions->append($item_deleteRegion);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'delete_region', $item_deleteRegion);

        my $item_deleteTempRegion = Gtk3::ImageMenuItem->new('Delete temporar_y regions');
        my $img_deleteTempRegion = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_deleteTempRegion->set_image($img_deleteTempRegion);
        $item_deleteTempRegion->signal_connect('activate' => sub {

            $self->deleteTempRegionsCallback();
        });
        $column_regions->append($item_deleteTempRegion);

        # Setup complete
        return $column_regions;
    }

    sub enableRoomsColumn {

        # Called by $self->enableMenu
        # Sets up the 'Rooms' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableRoomsColumn', @_);
        }

        # Set up column
        my $column_rooms = Gtk3::Menu->new();
        if (! $column_rooms) {

            return undef;
        }

        my $item_setCurrentRoom = Gtk3::MenuItem->new('_Set current room');
        $item_setCurrentRoom->signal_connect('activate' => sub {

            $self->mapObj->setCurrentRoom($self->selectedRoom);
        });
        $column_rooms->append($item_setCurrentRoom);
        # (Requires $self->currentRegionmap & $self->selectedRoom)
        $self->ivAdd('menuToolItemHash', 'set_current_room', $item_setCurrentRoom);

        my $item_unsetCurrentRoom = Gtk3::MenuItem->new('_Unset current room');
        $item_unsetCurrentRoom->signal_connect('activate' => sub {

            # This function automatically redraws the room
            $self->mapObj->setCurrentRoom();
        });
        $column_rooms->append($item_unsetCurrentRoom);
        # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
        $self->ivAdd('menuToolItemHash', 'unset_current_room', $item_unsetCurrentRoom);

        $column_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Locator task' submenu
            my $subMenu_locatorTask = Gtk3::Menu->new();

            my $item_resetLocator = Gtk3::MenuItem->new('_Reset Locator');
            $item_resetLocator->signal_connect('activate' => sub {

                $self->resetLocatorCallback();
            });
            $subMenu_locatorTask->append($item_resetLocator);
            # (Requires $self->currentRegionmap)
            $self->ivAdd('menuToolItemHash', 'reset_locator', $item_resetLocator);

            my $item_updateLocator = Gtk3::MenuItem->new('_Update Locator');
            $item_updateLocator->signal_connect('activate' => sub {

                # Update the Locator task
                $self->mapObj->updateLocator();
            });
            $subMenu_locatorTask->append($item_updateLocator);
            # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'update_locator', $item_updateLocator);

            $subMenu_locatorTask->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_setFacing = Gtk3::MenuItem->new('_Set facing direction...');
            $item_setFacing->signal_connect('activate' => sub {

                $self->setFacingCallback();
            });
            $subMenu_locatorTask->append($item_setFacing);

            my $item_resetFacing = Gtk3::MenuItem->new('R_eset facing direction...');
            $item_resetFacing->signal_connect('activate' => sub {

                $self->resetFacingCallback();
            });
            $subMenu_locatorTask->append($item_resetFacing);

            $subMenu_locatorTask->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_viewLocatorRoom = Gtk3::MenuItem->new('_View Locator room...');
            $item_viewLocatorRoom->signal_connect('activate' => sub {

                $self->editLocatorRoomCallback();
            });
            $subMenu_locatorTask->append($item_viewLocatorRoom);

        my $item_locatorTask = Gtk3::MenuItem->new('_Locator task');
        $item_locatorTask->set_submenu($subMenu_locatorTask);
        $column_rooms->append($item_locatorTask);

            # 'Pathfinding' submenu
            my $subMenu_pathFinding = Gtk3::Menu->new();

            my $item_highlightPath = Gtk3::MenuItem->new('_Highlight path');
            $item_highlightPath->signal_connect('activate' => sub {

                $self->processPathCallback('select_room');
            });
            $subMenu_pathFinding->append($item_highlightPath);
            # (Requires $self->currentRegionmap, $self->mapObj->currentRoom and $self->selectedRoom)
            $self->ivAdd('menuToolItemHash', 'path_finding_highlight', $item_highlightPath);

            my $item_displayPath = Gtk3::MenuItem->new('_Edit path...');
            $item_displayPath->signal_connect('activate' => sub {

                $self->processPathCallback('pref_win');
            });
            $subMenu_pathFinding->append($item_displayPath);
            # (Requires $self->currentRegionmap, $self->mapObj->currentRoom and $self->selectedRoom)
            $self->ivAdd('menuToolItemHash', 'path_finding_edit', $item_displayPath);

            my $item_goToRoom = Gtk3::MenuItem->new('_Go to room');
            $item_goToRoom->signal_connect('activate' => sub {

                $self->processPathCallback('send_char');
            });
            $subMenu_pathFinding->append($item_goToRoom);
            # (Requires $self->currentRegionmap, $self->mapObj->currentRoom and $self->selectedRoom)
            $self->ivAdd('menuToolItemHash', 'path_finding_go', $item_goToRoom);

            $subMenu_pathFinding->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_allowPostProcessing = Gtk3::CheckMenuItem->new('_Allow post-processing');
            $item_allowPostProcessing->set_active($self->worldModelObj->postProcessingFlag);
            $item_allowPostProcessing->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleFlag(
                    'postProcessingFlag',
                    $item_allowPostProcessing->get_active(),
                    FALSE,      # Don't call $self->redrawRegions
                    'allow_post_process',
                );
            });
            $subMenu_pathFinding->append($item_allowPostProcessing);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_post_process', $item_allowPostProcessing);

            my $item_avoidHazardousRooms = Gtk3::CheckMenuItem->new('A_void hazardous rooms');
            $item_avoidHazardousRooms->set_active($self->worldModelObj->avoidHazardsFlag);
            $item_avoidHazardousRooms->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleFlag(
                    'avoidHazardsFlag',
                    $item_avoidHazardousRooms->get_active(),
                    FALSE,      # Don't call $self->redrawRegions
                    'allow_hazard_rooms',
                );
            });
            $subMenu_pathFinding->append($item_avoidHazardousRooms);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_hazard_rooms', $item_avoidHazardousRooms);

            my $item_doubleClickPathFind = Gtk3::CheckMenuItem->new(
                'Allow _double-click moves',
            );
            $item_doubleClickPathFind->set_active($self->worldModelObj->quickPathFindFlag);
            $item_doubleClickPathFind->signal_connect('toggled' => sub {

                $self->worldModelObj->toggleFlag(
                    'quickPathFindFlag',
                    $item_doubleClickPathFind->get_active(),
                    FALSE,      # Don't call $self->redrawRegions
                    'allow_quick_path_find',
                );
            });
            $subMenu_pathFinding->append($item_doubleClickPathFind);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'allow_quick_path_find', $item_doubleClickPathFind);

            $subMenu_pathFinding->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_adjacentMode = Gtk3::MenuItem->new('_Set adjacent regions mode...');
            $item_adjacentMode->signal_connect('activate' => sub {

                $self->adjacentModeCallback();
            });
            $subMenu_pathFinding->append($item_adjacentMode);

        my $item_pathFinding = Gtk3::MenuItem->new('_Pathfinding');
        $item_pathFinding->set_submenu($subMenu_pathFinding);
        $column_rooms->append($item_pathFinding);

            # 'Move rooms/labels' submenu
            my $subMenu_moveRooms = Gtk3::Menu->new();

            my $item_moveSelected = Gtk3::MenuItem->new('Move in _direction...');
            $item_moveSelected->signal_connect('activate' => sub {

                $self->moveSelectedRoomsCallback();
            });
            $subMenu_moveRooms->append($item_moveSelected);
            # (Requires $self->currentRegionmap and one or more selected rooms)
            $self->ivAdd('menuToolItemHash', 'move_rooms_dir', $item_moveSelected);

            my $item_moveSelectedToClick = Gtk3::MenuItem->new('Move to _click');
            $item_moveSelectedToClick->signal_connect('activate' => sub {

                # Set the free clicking mode: $self->mouseClickEvent will move the objects when the
                #   user next clicks on an empty part of the map
                $self->set_freeClickMode('move_room');
            });
            $subMenu_moveRooms->append($item_moveSelectedToClick);
            # (Requires $self->currentRegionmap and one or more selected rooms)
            $self->ivAdd('menuToolItemHash', 'move_rooms_click', $item_moveSelectedToClick);

            $subMenu_moveRooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

                # 'Transfer to region' sub-submenu
                my $subSubMenu_transferRegion = Gtk3::Menu->new();

                if ($self->recentRegionList) {

                    foreach my $name ($self->recentRegionList) {

                        my $item_regionName = Gtk3::MenuItem->new($name);
                        $item_regionName->signal_connect('activate' => sub {

                            $self->transferSelectedRoomsCallback($name);
                        });
                        $subSubMenu_transferRegion->append($item_regionName);
                    }

                } else {

                    my $item_regionNone = Gtk3::MenuItem->new('(No recent regions)');
                    $item_regionNone->set_sensitive(FALSE);
                    $subSubMenu_transferRegion->append($item_regionNone);
                }

                $subSubMenu_transferRegion->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_transferSelect = Gtk3::MenuItem->new('Select region...');
                $item_transferSelect->signal_connect('activate' => sub {

                    $self->transferSelectedRoomsCallback();
                });
                $subSubMenu_transferRegion->append($item_transferSelect);

            my $item_transferRegion = Gtk3::MenuItem->new('_Transfer to region');
            $item_transferRegion->set_submenu($subSubMenu_transferRegion);
            $subMenu_moveRooms->append($item_transferRegion);
            # (Requires $self->currentRegionmap, one or more selected rooms and at least two regions
            #   in the world model)
            $self->ivAdd('menuToolItemHash', 'transfer_to_region', $item_transferRegion);

            $subMenu_moveRooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_mergeMoveRooms = Gtk3::MenuItem->new('_Merge/move rooms');
            $item_mergeMoveRooms->signal_connect('activate' => sub {

                $self->doMerge($self->mapObj->currentRoom);
            });
            $subMenu_moveRooms->append($item_mergeMoveRooms);
            # (Requires $self->currentRegionmap, a current room and the automapper object being set
            #   up to perform a merge)
            $self->ivAdd('menuToolItemHash', 'move_merge_rooms', $item_mergeMoveRooms);

        my $item_moveRooms = Gtk3::MenuItem->new('_Move rooms/labels');
        $item_moveRooms->set_submenu($subMenu_moveRooms);
        $column_rooms->append($item_moveRooms);
        # (Requires $self->currentRegionmap and EITHER one or more selected rooms OR a current room
        #   and the automapper being set up to perform a merge)
        $self->ivAdd('menuToolItemHash', 'move_rooms_labels', $item_moveRooms);

        $column_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Add room' submenu
            my $subMenu_addRoom = Gtk3::Menu->new();

            my $item_addFirstRoom = Gtk3::MenuItem->new('Add _first room');
            $item_addFirstRoom->signal_connect('activate' => sub {

                $self->addFirstRoomCallback();
            });
            $subMenu_addRoom->append($item_addFirstRoom);
            # (Requires $self->currentRegionmap & an empty $self->currentRegionmap->gridRoomHash)
            $self->ivAdd('menuToolItemHash', 'add_first_room', $item_addFirstRoom);

            my $item_addRoomAtClick = Gtk3::MenuItem->new('Add room at _click');
            $item_addRoomAtClick->signal_connect('activate' => sub {

                # Set the free clicking mode: $self->mouseClickEvent will create the new room when
                #   the user next clicks on an empty part of the map
                if ($self->currentRegionmap) {

                    $self->set_freeClickMode('add_room');
                }
            });
            $subMenu_addRoom->append($item_addRoomAtClick);

            my $item_addRoomAtBlock = Gtk3::MenuItem->new('Add room at _block...');
            $item_addRoomAtBlock->signal_connect('activate' => sub {

                $self->addRoomAtBlockCallback();
            });
            $subMenu_addRoom->append($item_addRoomAtBlock);

        my $item_addRoom = Gtk3::ImageMenuItem->new('Add _room');
        my $img_addRoom = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_addRoom->set_image($img_addRoom);
        $item_addRoom->set_submenu($subMenu_addRoom);
        $column_rooms->append($item_addRoom);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'add_room', $item_addRoom);

            # 'Add pattern' submenu
            my $subMenu_exitPatterns = Gtk3::Menu->new();

            my $item_addFailedExitWorld = Gtk3::MenuItem->new('Add failed exit to _world...');
            $item_addFailedExitWorld->signal_connect('activate' => sub {

                $self->addFailedExitCallback(TRUE);
            });
            $subMenu_exitPatterns->append($item_addFailedExitWorld);

            my $item_addFailedExitRoom = Gtk3::MenuItem->new('Add failed exit to current _room...');
            $item_addFailedExitRoom->signal_connect('activate' => sub {

                $self->addFailedExitCallback(FALSE, $self->mapObj->currentRoom);
            });
            $subMenu_exitPatterns->append($item_addFailedExitRoom);
            # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'add_failed_room', $item_addFailedExitRoom);

            $subMenu_exitPatterns->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addInvoluntaryExitRoom = Gtk3::MenuItem->new(
                'Add _involuntary exit to current room...',
            );
            $item_addInvoluntaryExitRoom->signal_connect('activate' => sub {

                $self->addInvoluntaryExitCallback($self->mapObj->currentRoom);
            });
            $subMenu_exitPatterns->append($item_addInvoluntaryExitRoom);
            # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'add_involuntary_exit', $item_addInvoluntaryExitRoom);

            my $item_addRepulseExitRoom = Gtk3::MenuItem->new(
                'Add r_epulse exit to current room...',
            );
            $item_addRepulseExitRoom->signal_connect('activate' => sub {

                $self->addRepulseExitCallback($self->mapObj->currentRoom);
            });
            $subMenu_exitPatterns->append($item_addRepulseExitRoom);
            # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'add_repulse_exit', $item_addRepulseExitRoom);

            $subMenu_exitPatterns->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addSpecialDepartRoom = Gtk3::MenuItem->new(
                'Add _special departure to current room...',
            );
            $item_addSpecialDepartRoom->signal_connect('activate' => sub {

                $self->addSpecialDepartureCallback($self->mapObj->currentRoom);
            });
            $subMenu_exitPatterns->append($item_addSpecialDepartRoom);
            # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'add_special_depart', $item_addSpecialDepartRoom);

            my $item_addUnspecifiedRoom = Gtk3::MenuItem->new(
                'Add _unspecified room pattern...',
            );
            $item_addUnspecifiedRoom->signal_connect('activate' => sub {

                $self->addUnspecifiedPatternCallback($self->mapObj->currentRoom);
            });
            $subMenu_exitPatterns->append($item_addUnspecifiedRoom);
            # (Requires $self->currentRegionmap & $self->mapObj->currentRoom)
            $self->ivAdd('menuToolItemHash', 'add_unspecified_pattern', $item_addUnspecifiedRoom);

        my $item_exitPatterns = Gtk3::MenuItem->new('Add p_attern');
        $item_exitPatterns->set_submenu($subMenu_exitPatterns);
        $column_rooms->append($item_exitPatterns);

            # 'Add to model' submenu
            my $subMenu_addToModel = Gtk3::Menu->new();

            my $item_addRoomContents = Gtk3::MenuItem->new('Add _contents...');
            $item_addRoomContents->signal_connect('activate' => sub {

                $self->addContentsCallback(FALSE);
            });
            $subMenu_addToModel->append($item_addRoomContents);
            # Requires $self->currentRegionmap, $self->mapObj->currentRoom
            $self->ivAdd('menuToolItemHash', 'add_room_contents', $item_addRoomContents);

            my $item_addContentsString = Gtk3::MenuItem->new('Add c_ontents from string...');
            $item_addContentsString->signal_connect('activate' => sub {

                $self->addContentsCallback(TRUE);
            });
            $subMenu_addToModel->append($item_addContentsString);
            # Requires $self->currentRegionmap, $self->selectedRoom
            $self->ivAdd('menuToolItemHash', 'add_contents_string', $item_addContentsString);

            $subMenu_addToModel->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addHiddenObj = Gtk3::MenuItem->new('Add _hidden object...');
            $item_addHiddenObj->signal_connect('activate' => sub {

                $self->addHiddenObjCallback(FALSE);
            });
            $subMenu_addToModel->append($item_addHiddenObj);
            # Requires $self->currentRegionmap, $self->mapObj->currentRoom
            $self->ivAdd('menuToolItemHash', 'add_hidden_object', $item_addHiddenObj);

            my $item_addHiddenString = Gtk3::MenuItem->new('Add h_idden object from string...');
            $item_addHiddenString->signal_connect('activate' => sub {

                $self->addHiddenObjCallback(TRUE);
            });
            $subMenu_addToModel->append($item_addHiddenString);
            # Requires $self->currentRegionmap, $self->selectedRoom
            $self->ivAdd('menuToolItemHash', 'add_hidden_string', $item_addHiddenString);

            $subMenu_addToModel->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addSearchResult = Gtk3::MenuItem->new('Add _search result...');
            $item_addSearchResult->signal_connect('activate' => sub {

                $self->addSearchResultCallback();
            });
            $subMenu_addToModel->append($item_addSearchResult);
            # Requires $self->currentRegionmap and $self->mapObj->currentRoom
            $self->ivAdd('menuToolItemHash', 'add_search_result', $item_addSearchResult);

        my $item_addToModel = Gtk3::MenuItem->new('Add to m_odel');
        $item_addToModel->set_submenu($subMenu_addToModel);
        $column_rooms->append($item_addToModel);
        # Requires $self->currentRegionmap and either $self->mapObj->currentRoom or
        #   $self->selectedRoom
        $self->ivAdd('menuToolItemHash', 'add_to_model', $item_addToModel);

        $column_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Add/set exits' submenu
            my $subMenu_setExits = Gtk3::Menu->new();

            my $item_addNormal = Gtk3::MenuItem->new('Add _normal exit...');
            $item_addNormal->signal_connect('activate' => sub {

                $self->addExitCallback(FALSE);  # FALSE - not a hidden exit
            });
            $subMenu_setExits->append($item_addNormal);
            # (Requires $self->currentRegionmap and a $self->selectedRoom whose ->wildMode is not
            #   'wild' - the value 'border' is ok, though)
            $self->ivAdd('menuToolItemHash', 'add_normal_exit', $item_addNormal);

            my $item_addHiddenExit = Gtk3::MenuItem->new('Add _hidden exit...');
            $item_addHiddenExit->signal_connect('activate' => sub {

                $self->addExitCallback(TRUE);   # TRUE - a hidden exit
            });
            $subMenu_setExits->append($item_addHiddenExit);
            # (Requires $self->currentRegionmap and a $self->selectedRoom whose ->wildMode is not
            #   'wild' - the value 'border' is ok, though)
            $self->ivAdd('menuToolItemHash', 'add_hidden_exit', $item_addHiddenExit);

            $subMenu_setExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addMultiple = Gtk3::MenuItem->new('Add _multiple exits...');
            $item_addMultiple->signal_connect('activate' => sub {

                $self->addMultipleExitsCallback();
            });
            $subMenu_setExits->append($item_addMultiple);
            # (Requires $self->currentRegionmap and one or more selected rooms)
            $self->ivAdd('menuToolItemHash', 'add_multiple_exits', $item_addMultiple);

            $subMenu_setExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_removeChecked = Gtk3::MenuItem->new('Remove _checked direction...');
            $item_removeChecked->signal_connect('activate' => sub {

                $self->removeCheckedDirCallback(FALSE);
            });
            $subMenu_setExits->append($item_removeChecked);
            # (Require a current regionmap, a single selected room that has one or more checked
            #   directions)
            $self->ivAdd('menuToolItemHash', 'remove_checked', $item_removeChecked);

            my $item_removeCheckedAll = Gtk3::MenuItem->new('Remove _all checked directions');
            $item_removeCheckedAll->signal_connect('activate' => sub {

                $self->removeCheckedDirCallback(TRUE);
            });
            $subMenu_setExits->append($item_removeCheckedAll);
            # (Require a current regionmap, a single selected room that has one or more checked
            #   directions)
            $self->ivAdd('menuToolItemHash', 'remove_checked_all', $item_removeCheckedAll);

            $subMenu_setExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_markNormal = Gtk3::MenuItem->new('Mark room(s) as n_ormal');
            $item_markNormal->signal_connect('activate' => sub {

                $self->setWildCallback('normal');
            });
            $subMenu_setExits->append($item_markNormal);
            # (Require a current regionmap and one or more selected rooms)
            $self->ivAdd('menuToolItemHash', 'wilderness_normal', $item_markNormal);

            my $item_markWild = Gtk3::MenuItem->new('Mark room(s) as _wilderness');
            $item_markWild->signal_connect('activate' => sub {

                $self->setWildCallback('wild');
            });
            $subMenu_setExits->append($item_markWild);
            # (Require a current regionmap, one or more selected rooms and
            #   $self->session->currentWorld->basicMappingFlag to be FALSE)
            $self->ivAdd('menuToolItemHash', 'wilderness_wild', $item_markWild);

            my $item_markBorder = Gtk3::MenuItem->new('Mark room(s) as wilderness _border');
            $item_markBorder->signal_connect('activate' => sub {

                $self->setWildCallback('border');
            });
            $subMenu_setExits->append($item_markBorder);
            # (Require a current regionmap, one or more selected rooms and
            #   $self->session->currentWorld->basicMappingFlag to be FALSE)
            $self->ivAdd('menuToolItemHash', 'wilderness_border', $item_markBorder);

        my $item_setExits = Gtk3::ImageMenuItem->new('Add/set _exits');
        my $img_setExits = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_setExits->set_image($img_setExits);
        $item_setExits->set_submenu($subMenu_setExits);
        $column_rooms->append($item_setExits);
        # (Require a current regionmap and one or more selected rooms)
        $self->ivAdd('menuToolItemHash', 'set_exits', $item_setExits);

        my $item_selectExit = Gtk3::MenuItem->new('Select e_xit in room...');
        $item_selectExit->signal_connect('activate' => sub {

            $self->selectExitCallback();
        });
        $column_rooms->append($item_selectExit);
        # (Requires $self->currentRegionmap & $self->selectedRoom)
        $self->ivAdd('menuToolItemHash', 'select_exit', $item_selectExit);

        $column_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_editRoom = Gtk3::ImageMenuItem->new('Ed_it room...');
        my $img_editRoom = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editRoom->set_image($img_editRoom);
        $item_editRoom->signal_connect('activate' => sub {

            # Open the room's 'edit' window
            $self->createFreeWin(
                'Games::Axmud::EditWin::ModelObj::Room',
                $self,
                $self->session,
                'Edit ' . $self->selectedRoom->category . ' model object',
                $self->selectedRoom,
                FALSE,                          # Not temporary
            );
        });
        $column_rooms->append($item_editRoom);
        # (Requires $self->currentRegionmap & $self->selectedRoom)
        $self->ivAdd('menuToolItemHash', 'edit_room', $item_editRoom);

            # 'Room text' submenu
            my $subMenu_roomText = Gtk3::Menu->new();

            my $item_setRoomTag = Gtk3::MenuItem->new('Set room _tag...');
            $item_setRoomTag->signal_connect('activate' => sub {

                $self->setRoomTagCallback();
            });
            $subMenu_roomText->append($item_setRoomTag);
            # (Requires $self->currentRegionmap and either $self->selectedRoom or
            #   $self->selectedRoomTag)
            $self->ivAdd('menuToolItemHash', 'set_room_tag', $item_setRoomTag);

            my $item_setGuild = Gtk3::MenuItem->new('Set room _guild...');
            $item_setGuild->signal_connect('activate' => sub {

                $self->setRoomGuildCallback();
            });
            $subMenu_roomText->append($item_setGuild);
            # (Requires $self->currentRegionmap and one or more of $self->selectedRoom,
            #   $self->selectedRoomHash, $self->selectedRoomGuild, $self->selectedRoomGuildHash)
            $self->ivAdd('menuToolItemHash', 'set_room_guild', $item_setGuild);

            $subMenu_roomText->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_resetPositions = Gtk3::MenuItem->new('_Reset text positions');
            $item_resetPositions->signal_connect('activate' => sub {

                $self->resetRoomOffsetsCallback();
            });
            $subMenu_roomText->append($item_resetPositions);
            # (Requires $self->currentRegionmap & $self->selectedRoom)
            $self->ivAdd('menuToolItemHash', 'reset_positions', $item_resetPositions);

            $subMenu_roomText->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_roomText = Gtk3::MenuItem->new('Set room _text');
        $item_roomText->set_submenu($subMenu_roomText);
        $column_rooms->append($item_roomText);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'room_text', $item_roomText);

            # 'Toggle room flag' submenu
            my $subMenu_toggleRoomFlag = Gtk3::Menu->new();

            if ($self->worldModelObj->roomFlagShowMode eq 'default') {

                # Show all room flags, sorted by filter
                foreach my $filter ($axmud::CLIENT->constRoomFilterList) {

                    # A sub-sub menu for $filter
                    my $subSubMenu_filter = Gtk3::Menu->new();

                    my @nameList = $self->worldModelObj->getRoomFlagsInFilter($filter);
                    foreach my $name (@nameList) {

                        my $obj = $self->worldModelObj->ivShow('roomFlagHash', $name);
                        if ($obj) {

                            my $menuItem = Gtk3::MenuItem->new($obj->descrip);
                            $menuItem->signal_connect('activate' => sub {

                                # Toggle the flags for all selected rooms, redraw them and (if the
                                #   flag is one of the hazardous room flags) recalculate the
                                #   regionmap's paths. The TRUE argument tells the world model to
                                #   redraw the rooms
                                $self->worldModelObj->toggleRoomFlags(
                                    $self->session,
                                    TRUE,
                                    $obj->name,
                                    $self->compileSelectedRooms(),
                                );
                            });
                            $subSubMenu_filter->append($menuItem);
                        }
                    }

                    if (! @nameList) {

                        my $menuItem = Gtk3::MenuItem->new('(No flags in this filter)');
                        $menuItem->set_sensitive(FALSE);
                        $subSubMenu_filter->append($menuItem);
                    }

                    my $menuItem = Gtk3::MenuItem->new(ucfirst($filter));
                    $menuItem->set_submenu($subSubMenu_filter);
                    $subMenu_toggleRoomFlag->append($menuItem);
                }

            } else {

                # Show selected room flags, sorted only by priority
                my %showHash = $self->worldModelObj->getVisibleRoomFlags();
                if (%showHash) {

                    foreach my $obj (sort {$a->priority <=> $b->priority} (values %showHash)) {

                        my $menuItem = Gtk3::MenuItem->new($obj->descrip);
                        $menuItem->signal_connect('activate' => sub {

                            # Toggle the flags for all selected rooms, redraw them and (if the
                            #   flag is one of the hazardous room flags) recalculate the
                            #   regionmap's paths. The TRUE argument tells the world model to
                            #   redraw the rooms
                            $self->worldModelObj->toggleRoomFlags(
                                $self->session,
                                TRUE,
                                $obj->name,
                                $self->compileSelectedRooms(),
                            );
                        });
                        $subMenu_toggleRoomFlag->append($menuItem);
                    }

                } else {

                    my $menuItem = Gtk3::MenuItem->new('(None are marked visible)');
                    $menuItem->set_sensitive(FALSE);
                    $subMenu_toggleRoomFlag->append($menuItem);
                }
            }

        my $item_toggleRoomFlag = Gtk3::MenuItem->new('Toggle room _flags');
        $item_toggleRoomFlag->set_submenu($subMenu_toggleRoomFlag);
        $column_rooms->append($item_toggleRoomFlag);
        # (Requires $self->currentRegionmap & either $self->selectedRoom or
        #   $self->selectedRoomHash)
        $self->ivAdd('menuToolItemHash', 'toggle_room_flag_sub', $item_toggleRoomFlag);

            # 'Other room features' submenu
            my $subMenu_roomFeatures = Gtk3::Menu->new();

                # 'Update character visits' sub-submenu
                my $subSubMenu_updateVisits = Gtk3::Menu->new();

                my $item_increaseSetCurrent = Gtk3::MenuItem->new('Increase & set _current');
                $item_increaseSetCurrent->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('increase');
                    $self->mapObj->setCurrentRoom($self->selectedRoom);
                });
                $subSubMenu_updateVisits->append($item_increaseSetCurrent);
                # (Requires $self->currentRegionmap and $self->selectedRoom)
                $self->ivAdd('menuToolItemHash', 'increase_set_current', $item_increaseSetCurrent);

                $subSubMenu_updateVisits->append(Gtk3::SeparatorMenuItem->new()); # Separator

                my $item_increaseVisits = Gtk3::MenuItem->new('_Increase by one');
                $item_increaseVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('increase');
                });
                $subSubMenu_updateVisits->append($item_increaseVisits);

                my $item_decreaseVisits = Gtk3::MenuItem->new('_Decrease by one');
                $item_decreaseVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('decrease');
                });
                $subSubMenu_updateVisits->append($item_decreaseVisits);

                my $item_manualVisits = Gtk3::MenuItem->new('Set _manually');
                $item_manualVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('manual');
                });
                $subSubMenu_updateVisits->append($item_manualVisits);

                my $item_resetVisits = Gtk3::MenuItem->new('_Reset to zero');
                $item_resetVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('reset');
                });
                $subSubMenu_updateVisits->append($item_resetVisits);

                $subSubMenu_updateVisits->append(Gtk3::SeparatorMenuItem->new()); # Separator

                my $item_toggleGraffiti = Gtk3::MenuItem->new('Toggle _graffiti');
                $item_toggleGraffiti->signal_connect('activate' => sub {

                    $self->toggleGraffitiCallback();
                });
                $subSubMenu_updateVisits->append($item_toggleGraffiti);
                # (Requires $self->currentRegionmap, $self->graffitiModeFlag & one or more selected
                #   rooms)
                $self->ivAdd('menuToolItemHash', 'toggle_graffiti', $item_toggleGraffiti);

            my $item_updateVisits = Gtk3::MenuItem->new('Update character _visits');
            $item_updateVisits->set_submenu($subSubMenu_updateVisits);
            $subMenu_roomFeatures->append($item_updateVisits);
            # (Requires $self->currentRegionmap & either $self->selectedRoom or
            #   $self->selectedRoomHash)
            $self->ivAdd('menuToolItemHash', 'update_visits', $item_updateVisits);

                # 'Room exclusivity' sub-submenu
                my $subSubMenu_exclusivity = Gtk3::Menu->new();

                my $item_toggleExclusivity = Gtk3::MenuItem->new('_Toggle exclusivity');
                $item_toggleExclusivity->signal_connect('activate' => sub {

                    $self->toggleExclusiveProfileCallback();
                });
                $subSubMenu_exclusivity->append($item_toggleExclusivity);
                # (Requires $self->currentRegionmap & either $self->selectedRoom or
                #   $self->selectedRoomHash)
                $self->ivAdd('menuToolItemHash', 'toggle_exclusivity', $item_toggleExclusivity);

                $subSubMenu_exclusivity->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_addExclusiveProf = Gtk3::MenuItem->new('_Add exclusive profile...');
                $item_addExclusiveProf->signal_connect('activate' => sub {

                    $self->addExclusiveProfileCallback();
                });
                $subSubMenu_exclusivity->append($item_addExclusiveProf);
                # (Requires $self->currentRegionmap & $self->selectedRoom)
                $self->ivAdd('menuToolItemHash', 'add_exclusive_prof', $item_addExclusiveProf);

                my $item_clearExclusiveProf = Gtk3::MenuItem->new('_Clear exclusive profiles');
                $item_clearExclusiveProf->signal_connect('activate' => sub {

                    $self->resetExclusiveProfileCallback();
                });
                $subSubMenu_exclusivity->append($item_clearExclusiveProf);
                # (Requires $self->currentRegionmap & either $self->selectedRoom or
                #   $self->selectedRoomHash)
                $self->ivAdd('menuToolItemHash', 'clear_exclusive_profs', $item_clearExclusiveProf);

            my $item_exclusivity = Gtk3::MenuItem->new('Room _exclusivity');
            $item_exclusivity->set_submenu($subSubMenu_exclusivity);
            $subMenu_roomFeatures->append($item_exclusivity);
            # (Requires $self->currentRegionmap & either $self->selectedRoom or
            #   $self->selectedRoomHash)
            $self->ivAdd('menuToolItemHash', 'room_exclusivity', $item_exclusivity);

                # 'Source code' sub-submenu
                my $subSubMenu_sourceCode = Gtk3::Menu->new();

                my $item_setFilePath = Gtk3::MenuItem->new('_Set file path...');
                $item_setFilePath->signal_connect('activate' => sub {

                    $self->setFilePathCallback();
                });
                $subSubMenu_sourceCode->append($item_setFilePath);
                # (Requires $self->currentRegionmap and $self->selectedRoom)
                $self->ivAdd('menuToolItemHash', 'set_file_path', $item_setFilePath);

                my $item_setVirtualArea = Gtk3::MenuItem->new('Set virtual _area...');
                $item_setVirtualArea->signal_connect('activate' => sub {

                    $self->setVirtualAreaCallback(TRUE);
                });
                $subSubMenu_sourceCode->append($item_setVirtualArea);
                # (Requires $self->currentRegionmap & either $self->selectedRoom or
                #   $self->selectedRoomHash)
                $self->ivAdd('menuToolItemHash', 'set_virtual_area', $item_setVirtualArea);

                my $item_resetVirtualArea = Gtk3::MenuItem->new('_Reset virtual area...');
                $item_resetVirtualArea->signal_connect('activate' => sub {

                    $self->setVirtualAreaCallback(FALSE);
                });
                $subSubMenu_sourceCode->append($item_resetVirtualArea);
                # (Requires $self->currentRegionmap & either $self->selectedRoom or
                #   $self->selectedRoomHash)
                $self->ivAdd('menuToolItemHash', 'reset_virtual_area', $item_resetVirtualArea);

                my $item_showSourceCode = Gtk3::MenuItem->new('S_how file paths');
                $item_showSourceCode->signal_connect('activate' => sub {

                    # (Don't use $self->pseudoCmdMode - we want to see the footer messages)
                    $self->session->pseudoCmd('listsourcecode', 'show_all');
                });
                $subSubMenu_sourceCode->append($item_showSourceCode);

                $subSubMenu_sourceCode->append(Gtk3::SeparatorMenuItem->new()); # Separator

                my $item_viewSourceCode = Gtk3::MenuItem->new('_View file...');
                $item_viewSourceCode->signal_connect('activate' => sub {

                    $self->quickFreeWin(
                        'Games::Axmud::OtherWin::SourceCode',
                        $self->session,
                        # Config
                        'model_obj' => $self->selectedRoom,
                    );
                });
                $subSubMenu_sourceCode->append($item_viewSourceCode);
                # (Requires $self->currentRegionmap, $self->selectedRoom &
                #   $self->selectedRoom->sourceCodePath & empty
                #   $self->selectedRoom->virtualAreaPath)
                $self->ivAdd('menuToolItemHash', 'view_source_code', $item_viewSourceCode);

                my $item_editSourceCode = Gtk3::MenuItem->new('_Edit file...');
                $item_editSourceCode->signal_connect('activate' => sub {

                    $self->editFileCallback();
                });
                $subSubMenu_sourceCode->append($item_editSourceCode);
                # (Requires $self->currentRegionmap, $self->selectedRoom &
                #   $self->selectedRoom->sourceCodePath & empty
                #   $self->selectedRoom->virtualAreaPath)
                $self->ivAdd('menuToolItemHash', 'edit_source_code', $item_editSourceCode);

                $subSubMenu_sourceCode->append(Gtk3::SeparatorMenuItem->new()); # Separator

                my $item_viewVirtualArea = Gtk3::MenuItem->new('View virtual area _file...');
                $item_viewVirtualArea->signal_connect('activate' => sub {

                    $self->quickFreeWin(
                        'Games::Axmud::OtherWin::SourceCode',
                        $self->session,
                        # Config
                        'model_obj' => $self->selectedRoom,
                        'virtual_flag' => TRUE,
                    );
                });
                $subSubMenu_sourceCode->append($item_viewVirtualArea);
                # (Requires $self->currentRegionmap, $self->selectedRoom &
                #   $self->selectedRoom->virtualAreaPath
                $self->ivAdd('menuToolItemHash', 'view_virtual_area', $item_viewVirtualArea);

                my $item_editVirtualArea = Gtk3::MenuItem->new('E_dit virtual area file...');
                $item_editVirtualArea->signal_connect('activate' => sub {

                    # Use TRUE to specify that the virtual area file should be opened
                    $self->editFileCallback(TRUE);
                });
                $subSubMenu_sourceCode->append($item_editVirtualArea);
                # (Requires $self->currentRegionmap, $self->selectedRoom &
                #   $self->selectedRoom->virtualAreaPath
                $self->ivAdd('menuToolItemHash', 'edit_virtual_area', $item_editVirtualArea);

            my $item_sourceCode = Gtk3::MenuItem->new('Source _code');
            $item_sourceCode->set_submenu($subSubMenu_sourceCode);
            $subMenu_roomFeatures->append($item_sourceCode);

            $subMenu_roomFeatures->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_setInteriorOffsets = Gtk3::MenuItem->new('_Synchronise grid coordinates...');
            $item_setInteriorOffsets->signal_connect('activate' => sub {

                $self->setInteriorOffsetsCallback();
            });
            $subMenu_roomFeatures->append($item_setInteriorOffsets);

            my $item_resetInteriorOffsets = Gtk3::MenuItem->new('_Reset grid coordinates');
            $item_resetInteriorOffsets->signal_connect('activate' => sub {

                $self->resetInteriorOffsetsCallback();
            });
            $subMenu_roomFeatures->append($item_resetInteriorOffsets);

        my $item_roomFeatures = Gtk3::MenuItem->new('Ot_her room features');
        $item_roomFeatures->set_submenu($subMenu_roomFeatures);
        $column_rooms->append($item_roomFeatures);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'other_room_features', $item_roomFeatures);

        $column_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_deleteRoom = Gtk3::ImageMenuItem->new('_Delete rooms');
        my $img_deleteRoom = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_deleteRoom->set_image($img_deleteRoom);
        $item_deleteRoom->signal_connect('activate' => sub {

            $self->deleteRoomsCallback();
        });
        $column_rooms->append($item_deleteRoom);
        # (Requires $self->currentRegionmap & either $self->selectedRoom or
        #   $self->selectedRoomHash)
        $self->ivAdd('menuToolItemHash', 'delete_room', $item_deleteRoom);

        # Setup complete
        return $column_rooms;
    }

    sub enableExitsColumn {

        # Called by $self->enableMenu
        # Sets up the 'Exits' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Local variables
        my @titleList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableExitsColumn', @_);
        }

        # Set up column
        my $column_exits = Gtk3::Menu->new();
        if (! $column_exits) {

            return undef;
        }

            # 'Set direction' submenu
            my $subMenu_setDir = Gtk3::Menu->new();

            my $item_changeDir = Gtk3::MenuItem->new('_Change direction...');
            $item_changeDir->signal_connect('activate' => sub {

                $self->changeDirCallback();
            });
            $subMenu_setDir->append($item_changeDir);
            # (Requires $self->currentRegionmap and $self->selectedExit and
            #   $self->selectedExit->drawMode is 'primary' or 'perm_alloc')
            $self->ivAdd('menuToolItemHash', 'change_direction', $item_changeDir);

            my $item_altDir = Gtk3::MenuItem->new('Set _alternative direction(s)...');
            $item_altDir->signal_connect('activate' => sub {

                $self->setAltDirCallback();
            });
            $subMenu_setDir->append($item_altDir);

        my $item_setDir = Gtk3::MenuItem->new('Set di_rection');
        $item_setDir->set_submenu($subMenu_setDir);
        $column_exits->append($item_setDir);
        # (Requires $self->currentRegionmap and $self->selectedExit)
        $self->ivAdd('menuToolItemHash', 'set_exit_dir', $item_setDir);

        my $item_setAssisted = Gtk3::MenuItem->new('Set assisted _move...');
        $item_setAssisted->signal_connect('activate' => sub {

            $self->setAssistedMoveCallback();
        });
        $column_exits->append($item_setAssisted);
        # (Requires $self->currentRegionmap and $self->selectedExit and
        #   $self->selectedExit->drawMode is 'primary', 'temp_unalloc' or 'perm_alloc')
        $self->ivAdd('menuToolItemHash', 'set_assisted_move', $item_setAssisted);

            # 'Allocate map direction' submenu
            my $subMenu_allocateMapDir = Gtk3::Menu->new();

            my $item_allocatePrimary = Gtk3::MenuItem->new('Choose _direction...');
            $item_allocatePrimary->signal_connect('activate' => sub {

                $self->allocateMapDirCallback();
            });
            $subMenu_allocateMapDir->append($item_allocatePrimary);

            my $item_confirmTwoWay = Gtk3::MenuItem->new('Confirm _two-way exit...');
            $item_confirmTwoWay->signal_connect('activate' => sub {

                $self->confirmTwoWayCallback();
            });
            $subMenu_allocateMapDir->append($item_confirmTwoWay);

        my $item_allocateMapDir = Gtk3::MenuItem->new('_Allocate map direction');
        $item_allocateMapDir->set_submenu($subMenu_allocateMapDir);
        $column_exits->append($item_allocateMapDir);
        # (Requires $self->currentRegionmap and $self->selectedExit and
        #   $self->selectedExit->drawMode is 'temp_alloc' or 'temp_unalloc')
        $self->ivAdd('menuToolItemHash', 'allocate_map_dir', $item_allocateMapDir);

        my $item_allocateShadow = Gtk3::MenuItem->new('Allocate _shadow...');
        $item_allocateShadow->signal_connect('activate' => sub {

            $self->allocateShadowCallback();
        });
        $column_exits->append($item_allocateShadow);
        # (Requires $self->currentRegionmap and $self->selectedExit and
        #   $self->selectedExit->drawMode is 'temp_alloc' or 'temp_unalloc')
        $self->ivAdd('menuToolItemHash', 'allocate_shadow', $item_allocateShadow);

        $column_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_connectExitToClick = Gtk3::MenuItem->new('_Connect to click');
        $item_connectExitToClick->signal_connect('activate' => sub {

            $self->connectToClickCallback();
        });
        $column_exits->append($item_connectExitToClick);
        # (Requires $self->currentRegionmap, $self->selectedExit and
        #   $self->selectedExit->drawMode 'primary', 'temp_unalloc' or 'perm_alloc')
        $self->ivAdd('menuToolItemHash', 'connect_to_click', $item_connectExitToClick);

        my $item_disconnectExit = Gtk3::MenuItem->new('D_isconnect exit');
        $item_disconnectExit->signal_connect('activate' => sub {

            $self->disconnectExitCallback();
        });
        $column_exits->append($item_disconnectExit);
        # (Requires $self->currentRegionmap and $self->selectedExit)
        $self->ivAdd('menuToolItemHash', 'disconnect_exit', $item_disconnectExit);

        $column_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Set ornaments' submenu
            my $subMenu_setOrnament = Gtk3::Menu->new();

            # Create a list of exit ornament types, in groups of two, in the form
            #   (menu_item_title, exit_ornament_type)
            @titleList = (
                '_No ornament', 'none',
                '_Openable exit', 'open',
                '_Lockable exit', 'lock',
                '_Pickable exit', 'pick',
                '_Breakable exit', 'break',
                '_Impassable exit', 'impass',
                '_Mystery exit', 'mystery',
            );

            do {

                my ($title, $type);

                $title = shift @titleList;
                $type = shift @titleList;

                my $menuItem = Gtk3::MenuItem->new($title);
                $menuItem->signal_connect('activate' => sub {

                    $self->exitOrnamentCallback($type);
                });
                $subMenu_setOrnament->append($menuItem);

            } until (! @titleList);

            $subMenu_setOrnament->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_setTwinOrnament = Gtk3::CheckMenuItem->new('Also set _twin exits');
            $item_setTwinOrnament->set_active($self->worldModelObj->setTwinOrnamentFlag);
            $item_setTwinOrnament->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'setTwinOrnamentFlag',
                        $item_setTwinOrnament->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'also_set_twin_exits',
                    );
                }
            });
            $subMenu_setOrnament->append($item_setTwinOrnament);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'also_set_twin_exits', $item_setTwinOrnament);

        my $item_setOrnament = Gtk3::MenuItem->new('Set _ornaments');
        $item_setOrnament->set_submenu($subMenu_setOrnament);
        $column_exits->append($item_setOrnament);
        # (Requires $self->currentRegionmap & either $self->selectedExit or
        #   $self->selectedExitHash)
        $self->ivAdd('menuToolItemHash', 'set_ornament_sub', $item_setOrnament);

            # 'Set exit type' submenu
            my $subMenu_setExitType = Gtk3::Menu->new();

                # 'Set hidden' sub-submenu
                my $subSubMenu_setHidden = Gtk3::Menu->new();

                my $item_setHiddenExit = Gtk3::MenuItem->new('Mark exit _hidden');
                $item_setHiddenExit->signal_connect('activate' => sub {

                    $self->hiddenExitCallback(TRUE);
                });
                $subSubMenu_setHidden->append($item_setHiddenExit);

                my $item_setNotHiddenExit = Gtk3::MenuItem->new('Mark exit _not hidden');
                $item_setNotHiddenExit->signal_connect('activate' => sub {

                    $self->hiddenExitCallback(FALSE);
                });
                $subSubMenu_setHidden->append($item_setNotHiddenExit);

            my $item_setHidden = Gtk3::MenuItem->new('Set _hidden');
            $item_setHidden->set_submenu($subSubMenu_setHidden);
            $subMenu_setExitType->append($item_setHidden);
            # (Requires $self->currentRegionmap and $self->selectedExit)
            $self->ivAdd('menuToolItemHash', 'set_hidden_sub', $item_setHidden);

                # 'Set broken' sub-submenu
                my $subSubMenu_setBroken = Gtk3::Menu->new();

                my $item_markBrokenExit = Gtk3::MenuItem->new('_Mark exit as broken');
                $item_markBrokenExit->signal_connect('activate' => sub {

                    $self->markBrokenExitCallback();
                });
                $subSubMenu_setBroken->append($item_markBrokenExit);

                my $item_toggleBrokenExit = Gtk3::MenuItem->new('_Toggle bent broken exit');
                $item_toggleBrokenExit->signal_connect('activate' => sub {

                    $self->worldModelObj->toggleBentExit(
                        TRUE,                       # Update Automapper windows now
                        $self->selectedExit,
                    );
                });
                $subSubMenu_setBroken->append($item_toggleBrokenExit);
                # (Requires $self->currentRegionmap and a $self->selectedExit which is a broken
                #   exit)
                $self->ivAdd('menuToolItemHash', 'toggle_bent_exit', $item_toggleBrokenExit);

                $subSubMenu_setBroken->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_restoreBrokenExit = Gtk3::MenuItem->new('_Restore unbroken exit');
                $item_restoreBrokenExit->signal_connect('activate' => sub {

                    $self->restoreBrokenExitCallback();
                });
                $subSubMenu_setBroken->append($item_restoreBrokenExit);

            my $item_setBroken = Gtk3::MenuItem->new('Set _broken');
            $item_setBroken->set_submenu($subSubMenu_setBroken);
            $subMenu_setExitType->append($item_setBroken);
            # (Requires $self->currentRegionmap and $self->selectedExit)
            $self->ivAdd('menuToolItemHash', 'set_broken_sub', $item_setBroken);

                # 'Set one-way' sub-submenu
                my $subSubMenu_setOneWay = Gtk3::Menu->new();

                my $item_markOneWayExit = Gtk3::MenuItem->new('_Mark exit as one-way');
                $item_markOneWayExit->signal_connect('activate' => sub {

                    $self->markOneWayExitCallback();
                });
                $subSubMenu_setOneWay->append($item_markOneWayExit);

                $subSubMenu_setOneWay->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_restoreUncertainExit = Gtk3::MenuItem->new('Restore _uncertain exit');
                $item_restoreUncertainExit->signal_connect('activate' => sub {

                    $self->restoreOneWayExitCallback(FALSE);
                });
                $subSubMenu_setOneWay->append($item_restoreUncertainExit);

                my $item_restoreTwoWayExit = Gtk3::MenuItem->new('Restore _two-way exit');
                $item_restoreTwoWayExit->signal_connect('activate' => sub {

                    $self->restoreOneWayExitCallback(TRUE);
                });
                $subSubMenu_setOneWay->append($item_restoreTwoWayExit);

                $subSubMenu_setOneWay->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_setIncomingDir = Gtk3::MenuItem->new('Set incoming _direction...');
                $item_setIncomingDir->signal_connect('activate' => sub {

                    $self->setIncomingDirCallback();
                });
                $subSubMenu_setOneWay->append($item_setIncomingDir);
                # (Requires $self->currentRegionmap and a $self->selectedExit which is a one-way
                #   exit)
                $self->ivAdd('menuToolItemHash', 'set_incoming_dir', $item_setIncomingDir);

            my $item_setOneWay = Gtk3::MenuItem->new('Set _one-way');
            $item_setOneWay->set_submenu($subSubMenu_setOneWay);
            $subMenu_setExitType->append($item_setOneWay);
            # (Requires $self->currentRegionmap and $self->selectedExit)
            $self->ivAdd('menuToolItemHash', 'set_oneway_sub', $item_setOneWay);

                # 'Set retracing' sub-submenu
                my $subSubMenu_setRetracing = Gtk3::Menu->new();

                my $item_markRetracingExit = Gtk3::MenuItem->new('_Mark exit as retracing');
                $item_markRetracingExit->signal_connect('activate' => sub {

                    $self->markRetracingExitCallback();
                });
                $subSubMenu_setRetracing->append($item_markRetracingExit);

                $subSubMenu_setRetracing->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_restoreRetracingExit = Gtk3::MenuItem->new('_Restore incomplete exit');
                $item_restoreRetracingExit->signal_connect('activate' => sub {

                    $self->restoreRetracingExitCallback();
                });
                $subSubMenu_setRetracing->append($item_restoreRetracingExit);

            my $item_setRetracing = Gtk3::MenuItem->new('Set _retracing');
            $item_setRetracing->set_submenu($subSubMenu_setRetracing);
            $subMenu_setExitType->append($item_setRetracing);
            # (Requires $self->currentRegionmap and $self->selectedExit)
            $self->ivAdd('menuToolItemHash', 'set_retracing_sub', $item_setRetracing);

                # 'Set random' sub-submenu
                my $subSubMenu_setRandomExit = Gtk3::Menu->new();

                my $item_markRandomRegion = Gtk3::MenuItem->new(
                    'Set random destination in same _region',
                );
                $item_markRandomRegion->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('same_region');
                });
                $subSubMenu_setRandomExit->append($item_markRandomRegion);

                my $item_markRandomAnywhere
                    = Gtk3::MenuItem->new('Set random destination _anywhere');
                $item_markRandomAnywhere->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('any_region');
                });
                $subSubMenu_setRandomExit->append($item_markRandomAnywhere);

                my $item_randomTempRegion
                    = Gtk3::MenuItem->new('_Create destination in temporary region');
                $item_randomTempRegion->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('temp_region');
                });
                $subSubMenu_setRandomExit->append($item_randomTempRegion);

                my $item_markRandomList = Gtk3::MenuItem->new('_Use list of random destinations');
                $item_markRandomList->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('room_list');
                });
                $subSubMenu_setRandomExit->append($item_markRandomList);

                $subSubMenu_setRandomExit->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_restoreRandomExit = Gtk3::MenuItem->new('Restore _incomplete exit');
                $item_restoreRandomExit->signal_connect('activate' => sub {

                    $self->restoreRandomExitCallback();
                });
                $subSubMenu_setRandomExit->append($item_restoreRandomExit);

            my $item_setRandomExit = Gtk3::MenuItem->new('Set r_andom');
            $item_setRandomExit->set_submenu($subSubMenu_setRandomExit);
            $subMenu_setExitType->append($item_setRandomExit);
            # (Requires $self->currentRegionmap and $self->selectedExit)
            $self->ivAdd('menuToolItemHash', 'set_random_sub', $item_setRandomExit);

                # 'Set super' sub-submenu
                my $subSubMenu_setSuperExit = Gtk3::Menu->new();

                my $item_markSuper = Gtk3::MenuItem->new('Mark exit as _super-region exit');
                $item_markSuper->signal_connect('activate' => sub {

                    $self->markSuperExitCallback(FALSE);
                });
                $subSubMenu_setSuperExit->append($item_markSuper);

                my $item_markSuperExcl = Gtk3::MenuItem->new(
                    'Mark exit as _exclusive super-region exit',
                );
                $item_markSuperExcl->signal_connect('activate' => sub {

                    $self->markSuperExitCallback(TRUE);
                });
                $subSubMenu_setSuperExit->append($item_markSuperExcl);

                $subSubMenu_setSuperExit->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_markNotSuper = Gtk3::MenuItem->new('Mark exit as _normal region exit');
                $item_markNotSuper->signal_connect('activate' => sub {

                    $self->restoreSuperExitCallback();
                });
                $subSubMenu_setSuperExit->append($item_markNotSuper);

            my $item_setSuperExit = Gtk3::MenuItem->new('Set _super');
            $item_setSuperExit->set_submenu($subSubMenu_setSuperExit);
            $subMenu_setExitType->append($item_setSuperExit);
            # (Requires $self->currentRegionmap and $self->selectedExit which is a region exit)
            $self->ivAdd('menuToolItemHash', 'set_super_sub', $item_setSuperExit);

            $subMenu_setExitType->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_setExitTwin = Gtk3::MenuItem->new('Set exit _twin...');
            $item_setExitTwin->signal_connect('activate' => sub {

                $self->setExitTwinCallback();
            });
            $subMenu_setExitType->append($item_setExitTwin);
            # (Requires $self->currentRegionmap and a $self->selectedExit which is either a one-way
            #   exit or an uncertain exit)
            $self->ivAdd('menuToolItemHash', 'set_exit_twin', $item_setExitTwin);

        my $item_setExitType = Gtk3::MenuItem->new('Set _exit type');
        $item_setExitType->set_submenu($subMenu_setExitType);
        $column_exits->append($item_setExitType);
        # (Requires $self->currentRegionmap and $self->selectedExit)
        $self->ivAdd('menuToolItemHash', 'set_exit_type', $item_setExitType);

            # 'Exit tags' submenu
            my $subMenu_exitTags = Gtk3::Menu->new();

            my $item_setExitText = Gtk3::MenuItem->new('_Edit tag text');
            $item_setExitText->signal_connect('activate' => sub {

                $self->editExitTagCallback();
            });
            $subMenu_exitTags->append($item_setExitText);
            # (Requires $self->currentRegionmap and either a $self->selectedExit which is a region
            #   exit, or a $self->selectedExitTag)
            $self->ivAdd('menuToolItemHash', 'edit_tag_text', $item_setExitText);

            my $item_toggleExitTag = Gtk3::MenuItem->new('_Toggle exit tag');
            $item_toggleExitTag->signal_connect('activate' => sub {

                $self->toggleExitTagCallback();
            });
            $subMenu_exitTags->append($item_toggleExitTag);
            # (Requires $self->currentRegionmap and either a $self->selectedExit which is a region
            #   exit, or a $self->selectedExitTag)
            $self->ivAdd('menuToolItemHash', 'toggle_exit_tag', $item_toggleExitTag);

            $subMenu_exitTags->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_resetPositions = Gtk3::MenuItem->new('_Reset tag positions');
            $item_resetPositions->signal_connect('activate' => sub {

                $self->resetExitOffsetsCallback();
            });
            $subMenu_exitTags->append($item_resetPositions);
            # (Requires $self->currentRegionmap and one or more of $self->selectedExit,
            #   $self->selectedExitHash, $self->selectedExitTag and $self->selectedExitTagHash)
            $self->ivAdd('menuToolItemHash', 'reset_exit_tags', $item_resetPositions);

            $subMenu_exitTags->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_applyExitTags = Gtk3::MenuItem->new('_Apply all tags in region');
            $item_applyExitTags->signal_connect('activate' => sub {

                $self->applyExitTagsCallback(TRUE);
            });
            $subMenu_exitTags->append($item_applyExitTags);


            my $item_cancelExitTags = Gtk3::MenuItem->new('_Cancel all tags in region');
            $item_cancelExitTags->signal_connect('activate' => sub {

                $self->applyExitTagsCallback(FALSE);
            });
            $subMenu_exitTags->append($item_cancelExitTags);

        my $item_exitTags = Gtk3::MenuItem->new('Exit _tags');
        $item_exitTags->set_submenu($subMenu_exitTags);
        $column_exits->append($item_exitTags);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'exit_tags', $item_exitTags);

        $column_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_editExit = Gtk3::ImageMenuItem->new('Edit e_xit...');
        my $img_editExit = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editExit->set_image($img_editExit);
        $item_editExit->signal_connect('activate' => sub {

            $self->editExitCallback();
        });
        $column_exits->append($item_editExit);
        # (Requires $self->currentRegionmap and $self->selectedExit)
        $self->ivAdd('menuToolItemHash', 'edit_exit', $item_editExit);

        $column_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Exit options' submenu
            my $subMenu_exitOptions = Gtk3::Menu->new();

            my $item_completeSelected = Gtk3::MenuItem->new('_Complete selected uncertain exits');
            $item_completeSelected->signal_connect('activate' => sub {

                $self->completeExitsCallback();
            });
            $subMenu_exitOptions->append($item_completeSelected);

            my $item_connectAdjacent = Gtk3::MenuItem->new('C_onnect selected adjacent rooms');
            $item_connectAdjacent->signal_connect('activate' => sub {

                $self->connectAdjacentCallback();
            });
            $subMenu_exitOptions->append($item_connectAdjacent);
            # (Requires $self->currentRegionmap and one or more selected rooms)
            $self->ivAdd('menuToolItemHash', 'connect_adjacent', $item_connectAdjacent);

            $subMenu_exitOptions->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_autocomplete = Gtk3::CheckMenuItem->new('_Autocomplete uncertain exits');
            $item_autocomplete->set_active($self->worldModelObj->autocompleteExitsFlag);
            $item_autocomplete->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'autocompleteExitsFlag',
                        $item_autocomplete->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'autcomplete_uncertain',
                    );
                }
            });
            $subMenu_exitOptions->append($item_autocomplete);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'autocomplete_uncertain', $item_autocomplete);

            my $item_intUncertain = Gtk3::CheckMenuItem->new('_Intelligent uncertain exits');
            $item_intUncertain->set_active(
                $self->worldModelObj->intelligentExitsFlag,
            );
            $item_intUncertain->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'intelligentExitsFlag',
                        $item_intUncertain->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'intelligent_uncertain',
                    );
                }
            });
            $subMenu_exitOptions->append($item_intUncertain);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'intelligent_uncertain', $item_intUncertain);

            $subMenu_exitOptions->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_collectChecked = Gtk3::CheckMenuItem->new('Co_llect checked directions');
            $item_collectChecked->set_active(
                $self->worldModelObj->collectCheckedDirsFlag,
            );
            $item_collectChecked->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'collectCheckedDirsFlag',
                        $item_collectChecked->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'collect_checked_dirs',
                    );
                }
            });
            $subMenu_exitOptions->append($item_collectChecked);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'collect_checked_dirs', $item_collectChecked);

            my $item_drawChecked = Gtk3::CheckMenuItem->new('_Draw checked directions');
            $item_drawChecked->set_active(
                $self->worldModelObj->drawCheckedDirsFlag,
            );
            $item_drawChecked->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'drawCheckedDirsFlag',
                        $item_drawChecked->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'draw_checked_dirs',
                    );
                }

                # Redraw the region, if one is visible
                if ($self->currentRegionmap) {

                    $self->redrawRegions();
                }
            });
            $subMenu_exitOptions->append($item_drawChecked);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'draw_checked_dirs', $item_drawChecked);

            $subMenu_exitOptions->append(Gtk3::SeparatorMenuItem->new());    # Separator

                 # 'Checkable directions' sub-submenu
                my $subSubMenu_checkable = Gtk3::Menu->new();

                my $item_checkableSimple
                    = Gtk3::RadioMenuItem->new_with_mnemonic(undef, 'Count _NSEW');
                $item_checkableSimple->signal_connect('toggled' => sub {

                    if ($item_checkableSimple->get_active) {

                        $self->worldModelObj->setCheckableDirMode('simple');
                    }
                });
                my $item_checkableGroup = $item_checkableSimple->get_group();
                $subSubMenu_checkable->append($item_checkableSimple);
                # (Never desensitised)
                $self->ivAdd('menuToolItemHash', 'checkable_dir_simple', $item_checkableSimple);

                my $item_checkableDiku = Gtk3::RadioMenuItem->new_with_mnemonic(
                    $item_checkableGroup,
                    'Count NSEW_UD',
                );
                if ($self->worldModelObj->checkableDirMode eq 'diku') {

                    $item_checkableDiku->set_active(TRUE);
                }
                $item_checkableDiku->signal_connect('toggled' => sub {

                    if ($item_checkableDiku->get_active) {

                        $self->worldModelObj->setCheckableDirMode('diku');
                    }
                });
                $subSubMenu_checkable->append($item_checkableDiku);
                # (Never desensitised)
                $self->ivAdd('menuToolItemHash', 'checkable_dir_diku', $item_checkableDiku);

                my $item_checkableLP = Gtk3::RadioMenuItem->new_with_mnemonic(
                    $item_checkableGroup,
                    'Count NSEWUD, N_E/NW/SE/SW',
                );
                if ($self->worldModelObj->checkableDirMode eq 'lp') {

                    $item_checkableLP->set_active(TRUE);
                }
                $item_checkableLP->signal_connect('toggled' => sub {

                    if ($item_checkableLP->get_active) {

                        $self->worldModelObj->setCheckableDirMode('lp');
                    }
                });
                $subSubMenu_checkable->append($item_checkableLP);
                # (Never desensitised)
                $self->ivAdd('menuToolItemHash', 'checkable_dir_lp', $item_checkableLP);

                my $item_checkableComplex = Gtk3::RadioMenuItem->new_with_mnemonic(
                    $item_checkableGroup,
                    'Count _all primary directions',
                );
                if ($self->worldModelObj->checkableDirMode eq 'complex') {

                    $item_checkableComplex->set_active(TRUE);
                }
                $item_checkableComplex->signal_connect('toggled' => sub {

                    if ($item_checkableComplex->get_active) {

                        $self->worldModelObj->setCheckableDirMode('complex');
                    }
                });
                $subSubMenu_checkable->append($item_checkableComplex);
                # (Never desensitised)
                $self->ivAdd('menuToolItemHash', 'checkable_dir_complex', $item_checkableComplex);

            my $item_exits = Gtk3::MenuItem->new('C_heckable directions');
            $item_exits->set_submenu($subSubMenu_checkable);
            $subMenu_exitOptions->append($item_exits);

        my $item_exitOptions = Gtk3::MenuItem->new('Exit o_ptions');
        $item_exitOptions->set_submenu($subMenu_exitOptions);
        $column_exits->append($item_exitOptions);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'exit_options', $item_exitOptions);

            # 'Exit lengths' submenu
            my $subMenu_exitLengths = Gtk3::Menu->new();

            my $item_horizontalLength = Gtk3::MenuItem->new('Set _horizontal length...');
            $item_horizontalLength->signal_connect('activate' => sub {

                $self->setExitLengthCallback('horizontal');
            });
            $subMenu_exitLengths->append($item_horizontalLength);

            my $item_verticalLength = Gtk3::MenuItem->new('Set _vertical length...');
            $item_verticalLength->signal_connect('activate' => sub {

                $self->setExitLengthCallback('vertical');
            });
            $subMenu_exitLengths->append($item_verticalLength);

            $subMenu_exitLengths->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_resetLength = Gtk3::MenuItem->new('_Reset exit lengths');
            $item_resetLength->signal_connect('activate' => sub {

                $self->resetExitLengthCallback();
            });
            $subMenu_exitLengths->append($item_resetLength);

        my $item_exitLengths = Gtk3::MenuItem->new('Exit _lengths');
        $item_exitLengths->set_submenu($subMenu_exitLengths);
        $column_exits->append($item_exitLengths);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'exit_lengths', $item_exitLengths);

        $column_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_deleteExit = Gtk3::ImageMenuItem->new('_Delete exit');
        my $img_deleteExit = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_deleteExit->set_image($img_deleteExit);
        $item_deleteExit->signal_connect('activate' => sub {

            $self->deleteExitCallback();
        });
        $column_exits->append($item_deleteExit);
        # (Requires $self->currentRegionmap and $self->selectedExit)
        $self->ivAdd('menuToolItemHash', 'delete_exit', $item_deleteExit);

        # Setup complete
        return $column_exits;
    }

    sub enableLabelsColumn {

        # Called by $self->enableMenu
        # Sets up the 'Labels' column of the Automapper window's menu bar
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Local variables
        my $alignFlag;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableLabelsColumn', @_);
        }

        # Set up column
        my $column_labels = Gtk3::Menu->new();
        if (! $column_labels) {

            return undef;
        }

        my $item_addLabelAtClick = Gtk3::ImageMenuItem->new('Add label at _click');
        my $img_addLabelAtClick = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_addLabelAtClick->set_image($img_addLabelAtClick);
        $item_addLabelAtClick->signal_connect('activate' => sub {

            # Set the free click mode; $self->canvasEventHandler will create the new label when the
            #   user next clicks on an empty part of the map
            if ($self->currentRegionmap) {

                $self->set_freeClickMode('add_label');
            }
        });
        $column_labels->append($item_addLabelAtClick);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'add_label_at_click', $item_addLabelAtClick);

        my $item_addLabelAtBlock = Gtk3::ImageMenuItem->new('Add label at _block');
        my $img_addLabelAtBlock = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_addLabelAtBlock->set_image($img_addLabelAtBlock);
        $item_addLabelAtBlock->signal_connect('activate' => sub {

            $self->addLabelAtBlockCallback();
        });
        $column_labels->append($item_addLabelAtBlock);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'add_label_at_block', $item_addLabelAtBlock);

        $column_labels->append(Gtk3::SeparatorMenuItem->new()); # Separator

        my $item_setLabel = Gtk3::ImageMenuItem->new('_Set label...');
        my $img_setLabel = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_setLabel->set_image($img_setLabel);
        $item_setLabel->signal_connect('activate' => sub {

            $self->setLabelCallback(FALSE);
        });
        $column_labels->append($item_setLabel);
        # (Requires $self->currentRegionmap and $self->selectedLabel)
        $self->ivAdd('menuToolItemHash', 'set_label', $item_setLabel);

        my $item_customiseLabel = Gtk3::ImageMenuItem->new('C_ustomise label...');
        my $img_customiseLabel = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_customiseLabel->set_image($img_customiseLabel);
        $item_customiseLabel->signal_connect('activate' => sub {

            $self->setLabelCallback(TRUE);
        });
        $column_labels->append($item_customiseLabel);
        # (Requires $self->currentRegionmap and $self->selectedLabel)
        $self->ivAdd('menuToolItemHash', 'customise_label', $item_customiseLabel);

            my $item_useMultiLine = Gtk3::CheckMenuItem->new('Use _multiline labels');
            $item_useMultiLine->set_active($self->worldModelObj->mapLabelTextViewFlag);
            $item_useMultiLine->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'mapLabelTextViewFlag',
                        $item_useMultiLine->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'use_multi_line',
                    );
                }
            });
            $column_labels->append($item_useMultiLine);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'use_multi_line', $item_useMultiLine);

        $column_labels->append(Gtk3::SeparatorMenuItem->new()); # Separator

        my $item_selectLabel = Gtk3::MenuItem->new('S_elect label...');
        $item_selectLabel->signal_connect('activate' => sub {

            $self->selectLabelCallback();
        });
        $column_labels->append($item_selectLabel);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'select_label', $item_selectLabel);

        $column_labels->append(Gtk3::SeparatorMenuItem->new()); # Separator

        my $item_addStyle = Gtk3::ImageMenuItem->new('_Add label style...');
        my $img_addStyle = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_addStyle->set_image($img_addStyle);
        $item_addStyle->signal_connect('activate' => sub {

            $self->addStyleCallback();
        });
        $column_labels->append($item_addStyle);

        my $item_editStyle = Gtk3::ImageMenuItem->new('Ed_it label style...');
        my $img_editStyle = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editStyle->set_image($img_editStyle);
        $item_editStyle->signal_connect('activate' => sub {

            $self->editStyleCallback();
        });
        $column_labels->append($item_editStyle);
        # (Requires at least one label style in $self->worldModelObj->mapLabelStyleHash)
        $self->ivAdd('menuToolItemHash', 'edit_style', $item_selectLabel);

            # 'Label alignment' submenu
            my $subMenu_alignment = Gtk3::Menu->new();

            my $item_alignHorizontal = Gtk3::CheckMenuItem->new('Align _horizontally');
            $item_alignHorizontal->set_active($self->worldModelObj->mapLabelAlignXFlag);
            $item_alignHorizontal->signal_connect('toggled' => sub {

                # Use $alignFlag to avoid an infinite loop, if we have to toggle the button back to
                #   its original state because the user declined to confirm the operation
                if (! $alignFlag) {

                    if (! $self->toggleAlignCallback('horizontal')) {

                        $alignFlag = TRUE;
                        if (! $item_alignHorizontal->get_active()) {
                            $item_alignHorizontal->set_active(TRUE);
                        } else {
                            $item_alignHorizontal->set_active(FALSE);
                        }

                        $alignFlag = FALSE;
                    }
                }
            });
            $subMenu_alignment->append($item_alignHorizontal);

            my $item_alignVertical = Gtk3::CheckMenuItem->new('Align _vertically');
            $item_alignVertical->set_active($self->worldModelObj->mapLabelAlignYFlag);
            $item_alignVertical->signal_connect('toggled' => sub {

                # Use $alignFlag to avoid an infinite loop, if we have to toggle the button back to
                #   its original state because the user declined to confirm the operation
                if (! $alignFlag) {

                    if (! $self->toggleAlignCallback('vertical')) {

                        $alignFlag = TRUE;
                        if (! $item_alignVertical->get_active()) {
                            $item_alignVertical->set_active(TRUE);
                        } else {
                            $item_alignVertical->set_active(FALSE);
                        }

                        $alignFlag = FALSE;
                    }
                }
            });
            $subMenu_alignment->append($item_alignVertical);

        my $item_alignment = Gtk3::MenuItem->new('_Label alignment');
        $item_alignment->set_submenu($subMenu_alignment);
        $column_labels->append($item_alignment);

        $column_labels->append(Gtk3::SeparatorMenuItem->new()); # Separator

        my $item_deleteLabel = Gtk3::ImageMenuItem->new('_Delete labels');
        my $img_deleteLabel = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_deleteLabel->set_image($img_deleteLabel);
        $item_deleteLabel->signal_connect('activate' => sub {

            # Callback to prompt for confirmation, before deleting multiple labels
            $self->deleteLabelsCallback();
        });
        $column_labels->append($item_deleteLabel);
        # (Requires $self->currentRegionmap & either $self->selectedLabel or
        #   $self->selectedLabelHash)
        $self->ivAdd('menuToolItemHash', 'delete_label', $item_deleteLabel);

        my $item_quickDelete = Gtk3::ImageMenuItem->new('_Quick label deletion...');
        my $img_quickDelete = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_quickDelete->set_image($img_quickDelete);
        $item_quickDelete->signal_connect('activate' => sub {

            $self->session->pseudoCmd('quicklabeldelete', $self->pseudoCmdMode);
        });
        $column_labels->append($item_quickDelete);

        # Setup complete
        return $column_labels;
    }

    # Popup menu widget methods

    sub enableCanvasPopupMenu {

        # Called by $self->canvasEventHandler
        # Creates a popup-menu for the Gtk3::Canvas when no rooms, exits, room tags or labels are
        #   selected
        #
        # Expected arguments
        #   $clickXPosPixels, $clickYPosPixels
        #       - Coordinates of the pixel that was right-clicked on the map
        #   $clickXPosBlocks, $clickYPosBlocks
        #       - Coordinates of the gridblock that was right-clicked on the map
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my (
            $self, $clickXPosPixels, $clickYPosPixels, $clickXPosBlocks, $clickYPosBlocks, $check,
        ) = @_;

        # Check for improper arguments
        if (
            ! defined $clickXPosPixels || ! defined $clickYPosPixels
            || ! defined $clickXPosBlocks || ! defined $clickYPosBlocks || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableCanvasPopupMenu', @_);
        }

        # Set up the popup menu
        my $menu_canvas = Gtk3::Menu->new();
        if (! $menu_canvas) {

            return undef;
        }

        # (Everything here assumes $self->currentRegionmap)

        my $item_addFirstRoom = Gtk3::ImageMenuItem->new('Add _first room');
        my $img_addFirstRoom = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_addFirstRoom->set_image($img_addFirstRoom);
        $item_addFirstRoom->signal_connect('activate' => sub {

            $self->addFirstRoomCallback();
        });
        $menu_canvas->append($item_addFirstRoom);
        # (Also requires empty $self->currentRegionmap->gridRoomHash)
        if ($self->currentRegionmap->gridRoomHash) {

            $item_addFirstRoom->set_sensitive(FALSE);
        }

        my $item_addRoomHere = Gtk3::ImageMenuItem->new('Add _room here');
        my $img_addRoomHere = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_addRoomHere->set_image($img_addRoomHere);
        $item_addRoomHere->signal_connect('activate' => sub {

            my $roomObj;

            # The 'Add room at click' operation from the main menu resets the value of
            #   ->freeClickMode; we must do the same here
            $self->reset_freeClickMode();

            # Create the room
            $roomObj = $self->mapObj->createNewRoom(
                $self->currentRegionmap,
                $clickXPosBlocks,
                $clickYPosBlocks,
                $self->currentRegionmap->currentLevel,
            );

            # When using the 'Add room at block' menu item, the new room is selected to make it
            #   easier to see where it was drawn. To make things consistent, select this new room,
            #   too
            if ($roomObj) {

                $self->setSelectedObj(
                    [$roomObj, 'room'],
                    FALSE,      # Select this object; unselect all other objects
                );
            }
        });
        $menu_canvas->append($item_addRoomHere);

        $menu_canvas->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_addLabelHere = Gtk3::ImageMenuItem->new('Add _label here');
        my $img_addLabelHere = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_addLabelHere->set_image($img_addLabelHere);
        $item_addLabelHere->signal_connect('activate' => sub {

            $self->addLabelAtClickCallback($clickXPosPixels, $clickYPosPixels);
        });
        $menu_canvas->append($item_addLabelHere);

        $menu_canvas->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_centreMap = Gtk3::MenuItem->new('_Centre map here');
        $item_centreMap->signal_connect('activate' => sub {

            $self->centreMapOverRoom(
                undef,              # Centre the map, not over a room...
                $clickXPosBlocks,   # ...but over this gridblock
                $clickYPosBlocks,
            );
        });
        $menu_canvas->append($item_centreMap);

        $menu_canvas->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_editRegionmap = Gtk3::ImageMenuItem->new('_Edit regionmap...');
        my $img_editRegionmap = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editRegionmap->set_image($img_editRegionmap);
        $item_editRegionmap->signal_connect('activate' => sub {

            # Open an 'edit' window for the regionmap
            $self->createFreeWin(
                'Games::Axmud::EditWin::Regionmap',
                $self,
                $self->session,
                'Edit \'' . $self->currentRegionmap->name . '\' regionmap',
                $self->currentRegionmap,
                FALSE,                          # Not temporary
            );
        });
        $menu_canvas->append($item_editRegionmap);

        my $item_preferences = Gtk3::ImageMenuItem->new('Edit world _model...');
        my $img_preferences = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_preferences->set_image($img_preferences);
        $item_preferences->signal_connect('activate' => sub {

            # Open an 'edit' window for the world model
            $self->createFreeWin(
                'Games::Axmud::EditWin::WorldModel',
                $self,
                $self->session,
                'Edit world model',
                $self->session->worldModelObj,
                FALSE,                          # Not temporary
            );
        });
        $menu_canvas->append($item_preferences);

        # Setup complete
        $menu_canvas->show_all();

        return $menu_canvas;
    }

    sub enableRoomsPopupMenu {

        # Called by $self->canvasObjEventHandler
        # Creates a popup-menu for the selected room
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableRoomsPopupMenu', @_);
        }

        # Set up the popup menu
        my $menu_rooms = Gtk3::Menu->new();
        if (! $menu_rooms) {

            return undef;
        }

        # (Everything here assumes $self->currentRegionmap and $self->selectedRoom)

        my $item_setCurrentRoom = Gtk3::MenuItem->new('_Set current room');
        $item_setCurrentRoom->signal_connect('activate' => sub {

            $self->mapObj->setCurrentRoom($self->selectedRoom);
        });
        $menu_rooms->append($item_setCurrentRoom);

        my $item_centreMap = Gtk3::MenuItem->new('_Centre map over room');
        $item_centreMap->signal_connect('activate' => sub {

            $self->centreMapOverRoom($self->selectedRoom);
        });
        $menu_rooms->append($item_centreMap);

        my $item_executeScripts = Gtk3::MenuItem->new('Run _Axbasic scripts');
        $item_executeScripts->signal_connect('activate' => sub {

            $self->executeScriptsCallback();
        });
        $menu_rooms->append($item_executeScripts);
        # (Also requires $self->mapObj->currentRoom that's the same as $self->selectedRoom)
        if (! $self->mapObj->currentRoom || $self->mapObj->currentRoom ne $self->selectedRoom) {

            $item_executeScripts->set_sensitive(FALSE);
        }

        $menu_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Pathfinding' submenu
            my $subMenu_pathFinding = Gtk3::Menu->new();

            my $item_highlightPath = Gtk3::MenuItem->new('_Highlight path');
            $item_highlightPath->signal_connect('activate' => sub {

                $self->processPathCallback('select_room');
            });
            $subMenu_pathFinding->append($item_highlightPath);

            my $item_displayPath = Gtk3::MenuItem->new('_Edit path...');
            $item_displayPath->signal_connect('activate' => sub {

                $self->processPathCallback('pref_win');
            });
            $subMenu_pathFinding->append($item_displayPath);

            my $item_goToRoom = Gtk3::MenuItem->new('_Go to room');
            $item_goToRoom->signal_connect('activate' => sub {

                $self->processPathCallback('send_char');
            });
            $subMenu_pathFinding->append($item_goToRoom);

        my $item_pathFinding = Gtk3::MenuItem->new('_Pathfinding');
        $item_pathFinding->set_submenu($subMenu_pathFinding);
        $menu_rooms->append($item_pathFinding);
        # (Also requires $self->mapObj->currentRoom)
        if (! $self->mapObj->currentRoom) {

            $item_pathFinding->set_sensitive(FALSE);
        }

            # 'Moves rooms/labels' submenu
            my $subMenu_moveRooms = Gtk3::Menu->new();

            my $item_moveSelected = Gtk3::MenuItem->new('Move in _direction...');
            $item_moveSelected->signal_connect('activate' => sub {

                $self->moveSelectedRoomsCallback();
            });
            $subMenu_moveRooms->append($item_moveSelected);

            my $item_moveSelectedToClick = Gtk3::MenuItem->new('Move to _click');
            $item_moveSelectedToClick->signal_connect('activate' => sub {

                # Set the free clicking mode: $self->mouseClickEvent will move the objects when the
                #   user next clicks on an empty part of the map
                $self->set_freeClickMode('move_room');
            });
            $subMenu_moveRooms->append($item_moveSelectedToClick);

            $subMenu_moveRooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

                # 'Transfer to region' sub-submenu
                my $subSubMenu_transferRegion = Gtk3::Menu->new();

                if ($self->recentRegionList) {

                    foreach my $name ($self->recentRegionList) {

                        my $item_regionName = Gtk3::MenuItem->new($name);
                        $item_regionName->signal_connect('activate' => sub {

                            $self->transferSelectedRoomsCallback($name);
                        });
                        $subSubMenu_transferRegion->append($item_regionName);
                    }

                } else {

                    my $item_regionNone = Gtk3::MenuItem->new('(No recent regions)');
                    $item_regionNone->set_sensitive(FALSE);
                    $subSubMenu_transferRegion->append($item_regionNone);
                }

                $subSubMenu_transferRegion->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_transferSelect = Gtk3::MenuItem->new('Select region...');
                $item_transferSelect->signal_connect('activate' => sub {

                    $self->transferSelectedRoomsCallback();
                });
                $subSubMenu_transferRegion->append($item_transferSelect);

            my $item_transferRegion = Gtk3::MenuItem->new('_Transfer to region');
            $item_transferRegion->set_submenu($subSubMenu_transferRegion);
            $subMenu_moveRooms->append($item_transferRegion);
            # (Also requires at least two regions in the world model)
            if ($self->worldModelObj->ivPairs('regionmapHash') <= 1) {

                $item_transferRegion->set_sensitive(FALSE);
            }

            $subMenu_moveRooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_mergeRoom = Gtk3::MenuItem->new('_Merge room');
            $item_mergeRoom->signal_connect('activate' => sub {

                $self->doMerge($self->mapObj->currentRoom);
            });
            $subMenu_moveRooms->append($item_mergeRoom);
            # (Also requires this to be the current room and the automapper being set up to perform
            #   a merge)
            if (
                ! $self->mapObj->currentRoom
                || $self->mapObj->currentRoom ne $self->selectedRoom
                || ! $self->mapObj->currentMatchFlag
            ) {
                $item_mergeRoom->set_sensitive(FALSE);
            }

                # 'Compare room' sub-submenu
                my $subSubMenu_compareRoom = Gtk3::Menu->new();

                my $item_compareRoomRegion = Gtk3::MenuItem->new('...with rooms in region');
                $item_compareRoomRegion->signal_connect('activate' => sub {

                    $self->compareRoomCallback(FALSE);
                });
                $subSubMenu_compareRoom->append($item_compareRoomRegion);

                my $item_compareRoomModel = Gtk3::MenuItem->new('...with rooms in whole world');
                $item_compareRoomModel->signal_connect('activate' => sub {

                    $self->compareRoomCallback(TRUE);
                });
                $subSubMenu_compareRoom->append($item_compareRoomModel);

            my $item_compareRoom = Gtk3::MenuItem->new('_Compare room');
            $item_compareRoom->set_submenu($subSubMenu_compareRoom);
            $subMenu_moveRooms->append($item_compareRoom);

        my $item_moveRooms = Gtk3::MenuItem->new('_Move rooms/labels');
        $item_moveRooms->set_submenu($subMenu_moveRooms);
        $menu_rooms->append($item_moveRooms);

            # 'Add pattern' submenu
            my $subMenu_exitPatterns = Gtk3::Menu->new();

            my $item_addFailedExitRoom = Gtk3::MenuItem->new('Add _failed exit...');
            $item_addFailedExitRoom->signal_connect('activate' => sub {

                $self->addFailedExitCallback(FALSE, $self->selectedRoom);
            });
            $subMenu_exitPatterns->append($item_addFailedExitRoom);

            $subMenu_exitPatterns->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_addInvoluntaryExitRoom = Gtk3::MenuItem->new('Add _involuntary exit...');
            $item_addInvoluntaryExitRoom->signal_connect('activate' => sub {

                $self->addInvoluntaryExitCallback($self->selectedRoom);
            });
            $subMenu_exitPatterns->append($item_addInvoluntaryExitRoom);

            my $item_addRepulseExitRoom = Gtk3::MenuItem->new('Add _repulse exit...');
            $item_addRepulseExitRoom->signal_connect('activate' => sub {

                $self->addRepulseExitCallback($self->selectedRoom);
            });
            $subMenu_exitPatterns->append($item_addRepulseExitRoom);

            $subMenu_exitPatterns->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_addSpecialDepartRoom = Gtk3::MenuItem->new('Add _special departure...');
            $item_addSpecialDepartRoom->signal_connect('activate' => sub {

                $self->addSpecialDepartureCallback($self->selectedRoom);
            });
            $subMenu_exitPatterns->append($item_addSpecialDepartRoom);

            my $item_addUnspecifiedRoom = Gtk3::MenuItem->new('Add _unspecified room pattern...');
            $item_addUnspecifiedRoom->signal_connect('activate' => sub {

                $self->addUnspecifiedPatternCallback($self->selectedRoom);
            });
            $subMenu_exitPatterns->append($item_addUnspecifiedRoom);

        $menu_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_patterns = Gtk3::MenuItem->new('Add pa_ttern');
        $item_patterns->set_submenu($subMenu_exitPatterns);
        $menu_rooms->append($item_patterns);

            # 'Add to model' submenu
            my $subMenu_addToModel = Gtk3::Menu->new();

            my $item_addRoomContents = Gtk3::MenuItem->new('Add _contents...');
            $item_addRoomContents->signal_connect('activate' => sub {

                $self->addContentsCallback(FALSE);
            });
            $subMenu_addToModel->append($item_addRoomContents);
            # (Also requires $self->mapObj->currentRoom that's the same as $self->selectedRoom
            if (! $self->mapObj->currentRoom || $self->mapObj->currentRoom ne $self->selectedRoom) {

                $item_addRoomContents->set_sensitive(FALSE);
            }

            my $item_addContentsString = Gtk3::MenuItem->new('Add c_ontents from string...');
            $item_addContentsString->signal_connect('activate' => sub {

                $self->addContentsCallback(TRUE);
            });
            $subMenu_addToModel->append($item_addContentsString);

            $subMenu_addToModel->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addHiddenObj = Gtk3::MenuItem->new('Add _hidden object...');
            $item_addHiddenObj->signal_connect('activate' => sub {

                $self->addHiddenObjCallback(FALSE);
            });
            $subMenu_addToModel->append($item_addHiddenObj);
            # (Also requires $self->mapObj->currentRoom that's the same as $self->selectedRoom
            if (! $self->mapObj->currentRoom || $self->mapObj->currentRoom ne $self->selectedRoom) {

                $item_addHiddenObj->set_sensitive(FALSE);
            }

            my $item_addHiddenString = Gtk3::MenuItem->new('Add h_idden object from string...');
            $item_addHiddenString->signal_connect('activate' => sub {

                $self->addHiddenObjCallback(TRUE);
            });
            $subMenu_addToModel->append($item_addHiddenString);

            $subMenu_addToModel->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addSearchResult = Gtk3::MenuItem->new('Add _search result...');
            $item_addSearchResult->signal_connect('activate' => sub {

                $self->addSearchResultCallback();
            });
            $subMenu_addToModel->append($item_addSearchResult);
            # (Also requires $self->mapObj->currentRoom that's the same as $self->selectedRoom)
            if (! $self->mapObj->currentRoom || $self->mapObj->currentRoom ne $self->selectedRoom) {

                $item_addSearchResult->set_sensitive(FALSE);
            }

        my $item_addToModel = Gtk3::MenuItem->new('Add to m_odel');
        $item_addToModel->set_submenu($subMenu_addToModel);
        $menu_rooms->append($item_addToModel);

        $menu_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

             # 'Add/set exits' submenu
            my $subMenu_setExits = Gtk3::Menu->new();

            my $item_addExit = Gtk3::MenuItem->new('Add _normal exit...');
            $item_addExit->signal_connect('activate' => sub {

                $self->addExitCallback(FALSE);      # FALSE - not a hidden exit
            });
            $subMenu_setExits->append($item_addExit);
            # (Also requires the selected room's ->wildMode to be 'normal' or 'border')
            if ($self->selectedRoom->wildMode eq 'wild') {

                $item_addExit->set_sensitive(FALSE);
            }

            my $item_addHiddenExit = Gtk3::MenuItem->new('Add _hidden exit...');
            $item_addHiddenExit->signal_connect('activate' => sub {

                $self->addExitCallback(TRUE);       # TRUE - a hidden exit
            });
            $subMenu_setExits->append($item_addHiddenExit);
            # (Also requires the selected room's ->wildMode to be 'normal' or 'border')
            if ($self->selectedRoom->wildMode eq 'wild') {

                $item_addHiddenExit->set_sensitive(FALSE);
            }

            $subMenu_setExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_addMultiple = Gtk3::MenuItem->new('Add _multiple exits...');
            $item_addMultiple->signal_connect('activate' => sub {

                $self->addMultipleExitsCallback();
            });
            $subMenu_setExits->append($item_addMultiple);

            $subMenu_setExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_removeChecked = Gtk3::MenuItem->new('Remove _checked direction...');
            $item_removeChecked->signal_connect('activate' => sub {

                $self->removeCheckedDirCallback(FALSE);
            });
            $subMenu_setExits->append($item_removeChecked);
            # (Also requires the selected room's ->checkedDirHash to be non-empty)
            if (! $self->selectedRoom->checkedDirHash) {

                $item_removeChecked->set_sensitive(FALSE);
            }

            my $item_removeCheckedAll = Gtk3::MenuItem->new('Remove _all checked directions');
            $item_removeCheckedAll->signal_connect('activate' => sub {

                $self->removeCheckedDirCallback(TRUE);
            });
            $subMenu_setExits->append($item_removeCheckedAll);
            # (Also requires the selected room's ->checkedDirHash to be non-empty)
            if (! $self->selectedRoom->checkedDirHash) {

                $item_removeCheckedAll->set_sensitive(FALSE);
            }

            $subMenu_setExits->append(Gtk3::SeparatorMenuItem->new()); # Separator

            my $item_markNormal = Gtk3::MenuItem->new('Mark room as n_ormal');
            $item_markNormal->signal_connect('activate' => sub {

                $self->setWildCallback('normal');
            });
            $subMenu_setExits->append($item_markNormal);

            my $item_markWild = Gtk3::MenuItem->new('Mark room as _wilderness');
            $item_markWild->signal_connect('activate' => sub {

                $self->setWildCallback('wild');
            });
            $subMenu_setExits->append($item_markWild);
            # (Also requires $self->session->currentWorld->basicMappingFlag to be FALSE)
            if ($self->session->currentWorld->basicMappingFlag) {

                $item_markWild->set_sensitive(FALSE);
            }

            my $item_markBorder = Gtk3::MenuItem->new('Mark room as wilderness _border');
            $item_markBorder->signal_connect('activate' => sub {

                $self->setWildCallback('border');
            });
            $subMenu_setExits->append($item_markBorder);
            # (Also requires $self->session->currentWorld->basicMappingFlag to be FALSE)
            if ($self->session->currentWorld->basicMappingFlag) {

                $item_markBorder->set_sensitive(FALSE);
            }

        my $item_setExits = Gtk3::ImageMenuItem->new('Add/set _exits');
        my $img_setExits = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_setExits->set_image($img_setExits);
        $item_setExits->set_submenu($subMenu_setExits);
        $menu_rooms->append($item_setExits);

        my $item_selectExit = Gtk3::MenuItem->new('Se_lect exit...');
        $item_selectExit->signal_connect('activate' => sub {

            $self->selectExitCallback();
        });
        $menu_rooms->append($item_selectExit);

        $menu_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_editRoom = Gtk3::ImageMenuItem->new('Ed_it room...');
        my $img_editRoom = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editRoom->set_image($img_editRoom);
        $item_editRoom->signal_connect('activate' => sub {

            if ($self->selectedRoom) {

                # Open the room's 'edit' window
                $self->createFreeWin(
                    'Games::Axmud::EditWin::ModelObj::Room',
                    $self,
                    $self->session,
                    'Edit ' . $self->selectedRoom->category . ' model object',
                    $self->selectedRoom,
                    FALSE,                          # Not temporary
                );
            }
        });
        $menu_rooms->append($item_editRoom);

            # 'Set room text' submenu
            my $subMenu_setRoomText = Gtk3::Menu->new();

            my $item_setRoomTag = Gtk3::MenuItem->new('Set room _tag...');
            $item_setRoomTag->signal_connect('activate' => sub {

                $self->setRoomTagCallback();
            });
            $subMenu_setRoomText->append($item_setRoomTag);

            my $item_setGuild = Gtk3::MenuItem->new('Set room _guild...');
            $item_setGuild->signal_connect('activate' => sub {

                $self->setRoomGuildCallback();
            });
            $subMenu_setRoomText->append($item_setGuild);

            $subMenu_setRoomText->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_resetPositions = Gtk3::MenuItem->new('_Reset text posit_ions');
            $item_resetPositions->signal_connect('activate' => sub {

                $self->resetRoomOffsetsCallback();
            });
            $subMenu_setRoomText->append($item_resetPositions);

        my $item_setRoomText = Gtk3::MenuItem->new('Set _room text');
        $item_setRoomText->set_submenu($subMenu_setRoomText);
        $menu_rooms->append($item_setRoomText);

            # 'Toggle room flag' submenu
            my $subMenu_toggleRoomFlag = Gtk3::Menu->new();

            if ($self->worldModelObj->roomFlagShowMode eq 'default') {

                # Show all room flags, sorted by filter
                foreach my $filter ($axmud::CLIENT->constRoomFilterList) {

                    # A sub-sub menu for $filter
                    my $subSubMenu_filter = Gtk3::Menu->new();

                    my @nameList = $self->worldModelObj->getRoomFlagsInFilter($filter);
                    foreach my $name (@nameList) {

                        my $obj = $self->worldModelObj->ivShow('roomFlagHash', $name);
                        if ($obj) {

                            my $menuItem = Gtk3::MenuItem->new($obj->descrip);
                            $menuItem->signal_connect('activate' => sub {

                                # Toggle the flags for all selected rooms, redraw them and (if the
                                #   flag is one of the hazardous room flags) recalculate the
                                #   regionmap's paths. The TRUE argument tells the world model to
                                #   redraw the rooms
                                $self->worldModelObj->toggleRoomFlags(
                                    $self->session,
                                    TRUE,
                                    $obj->name,
                                    $self->compileSelectedRooms(),
                                );
                            });
                            $subSubMenu_filter->append($menuItem);
                        }
                    }

                    if (! @nameList) {

                        my $menuItem = Gtk3::MenuItem->new('(No flags in this filter)');
                        $menuItem->set_sensitive(FALSE);
                        $subSubMenu_filter->append($menuItem);
                    }

                    my $menuItem = Gtk3::MenuItem->new(ucfirst($filter));
                    $menuItem->set_submenu($subSubMenu_filter);
                    $subMenu_toggleRoomFlag->append($menuItem);
                }

            } else {

                # Show selected room flags, sorted only by priority
                my %showHash = $self->worldModelObj->getVisibleRoomFlags();
                if (%showHash) {

                    foreach my $obj (sort {$a->priority <=> $b->priority} (values %showHash)) {

                        my $menuItem = Gtk3::MenuItem->new($obj->descrip);
                        $menuItem->signal_connect('activate' => sub {

                            # Toggle the flags for all selected rooms, redraw them and (if the
                            #   flag is one of the hazardous room flags) recalculate the
                            #   regionmap's paths. The TRUE argument tells the world model to
                            #   redraw the rooms
                            $self->worldModelObj->toggleRoomFlags(
                                $self->session,
                                TRUE,
                                $obj->name,
                                $self->compileSelectedRooms(),
                            );
                        });
                        $subMenu_toggleRoomFlag->append($menuItem);
                    }

                } else {

                    my $menuItem = Gtk3::MenuItem->new('(None are marked visible)');
                    $menuItem->set_sensitive(FALSE);
                    $subMenu_toggleRoomFlag->append($menuItem);
                }
            }

        my $item_toggleRoomFlag = Gtk3::MenuItem->new('To_ggle room flags');
        $item_toggleRoomFlag->set_submenu($subMenu_toggleRoomFlag);
        $menu_rooms->append($item_toggleRoomFlag);

            # 'Other room features' submenu
            my $subMenu_roomFeatures = Gtk3::Menu->new();

                # 'Update character visits' sub-submenu
                my $subSubMenu_updateVisits = Gtk3::Menu->new();

                my $item_increaseSetCurrent = Gtk3::MenuItem->new('Increase & set _current');
                $item_increaseSetCurrent->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('increase');
                    $self->mapObj->setCurrentRoom($self->selectedRoom);
                });
                $subSubMenu_updateVisits->append($item_increaseSetCurrent);

                $subSubMenu_updateVisits->append(Gtk3::SeparatorMenuItem->new()); # Separator

                my $item_increaseVisits = Gtk3::MenuItem->new('_Increase by one');
                $item_increaseVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('increase');
                });
                $subSubMenu_updateVisits->append($item_increaseVisits);

                my $item_decreaseVisits = Gtk3::MenuItem->new('_Decrease by one');
                $item_decreaseVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('decrease');
                });
                $subSubMenu_updateVisits->append($item_decreaseVisits);

                my $item_manualVisits = Gtk3::MenuItem->new('Set _manually');
                $item_manualVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('manual');
                });
                $subSubMenu_updateVisits->append($item_manualVisits);

                my $item_resetVisits = Gtk3::MenuItem->new('_Reset to zero');
                $item_resetVisits->signal_connect('activate' => sub {

                    $self->updateVisitsCallback('reset');
                });
                $subSubMenu_updateVisits->append($item_resetVisits);

                $subSubMenu_updateVisits->append(Gtk3::SeparatorMenuItem->new()); # Separator

                my $item_toggleGraffiti = Gtk3::MenuItem->new('Toggle _graffiti');
                $item_toggleGraffiti->signal_connect('activate' => sub {

                    $self->toggleGraffitiCallback();
                });
                $subSubMenu_updateVisits->append($item_toggleGraffiti);
                # (Also requires $self->graffitiModeFlag)
                if (! $self->graffitiModeFlag) {

                    $item_toggleGraffiti->set_sensitive(FALSE);
                }

            my $item_updateVisits = Gtk3::MenuItem->new('Update character _visits');
            $item_updateVisits->set_submenu($subSubMenu_updateVisits);
            $subMenu_roomFeatures->append($item_updateVisits);

            # 'Room exclusivity' submenu
            my $subMenu_exclusivity = Gtk3::Menu->new();

                my $item_toggleExclusivity = Gtk3::MenuItem->new('_Toggle exclusivity');
                $item_toggleExclusivity->signal_connect('activate' => sub {

                    $self->toggleExclusiveProfileCallback();
                });
                $subMenu_exclusivity->append($item_toggleExclusivity);

                my $item_addExclusiveProf = Gtk3::MenuItem->new('_Add exclusive profile...');
                $item_addExclusiveProf->signal_connect('activate' => sub {

                    $self->addExclusiveProfileCallback();
                });
                $subMenu_exclusivity->append($item_addExclusiveProf);

                my $item_clearExclusiveProf = Gtk3::MenuItem->new('_Clear exclusive profiles');
                $item_clearExclusiveProf->signal_connect('activate' => sub {

                    $self->resetExclusiveProfileCallback();
                });
                $subMenu_exclusivity->append($item_clearExclusiveProf);

            my $item_exclusivity = Gtk3::MenuItem->new('Room _exclusivity');
            $item_exclusivity->set_submenu($subMenu_exclusivity);
            $subMenu_roomFeatures->append($item_exclusivity);

                # 'Source code' sub-submenu
                my $subSubMenu_sourceCode = Gtk3::Menu->new();

                my $item_setFilePath = Gtk3::MenuItem->new('_Set file path...');
                $item_setFilePath->signal_connect('activate' => sub {

                    $self->setFilePathCallback();
                });
                $subSubMenu_sourceCode->append($item_setFilePath);

                my $item_setVirtualArea = Gtk3::MenuItem->new('Set virtual _area...');
                $item_setVirtualArea->signal_connect('activate' => sub {

                    $self->setVirtualAreaCallback(TRUE);
                });
                $subSubMenu_sourceCode->append($item_setVirtualArea);

                my $item_resetVirtualArea = Gtk3::MenuItem->new('_Reset virtual area...');
                $item_resetVirtualArea->signal_connect('activate' => sub {

                    $self->setVirtualAreaCallback(FALSE);
                });
                $subSubMenu_sourceCode->append($item_resetVirtualArea);

                $subSubMenu_sourceCode->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_viewSource = Gtk3::MenuItem->new('_View source file...');
                $item_viewSource->signal_connect('activate' => sub {

                    my $flag;

                    if ($self->selectedRoom) {

                        if (! $self->selectedRoom->virtualAreaPath) {
                            $flag = FALSE;
                        } else {
                            $flag = TRUE;
                        }

                        # Show source code file
                        $self->quickFreeWin(
                            'Games::Axmud::OtherWin::SourceCode',
                            $self->session,
                            # Config
                            'model_obj' => $self->selectedRoom,
                            'virtual_flag' => $flag,
                        );
                    }
                });
                $subSubMenu_sourceCode->append($item_viewSource);
                # (Also requires either $self->selectedRoom->sourceCodePath or
                #   $self->selectedRoom->virtualAreaPath)
                if (
                    ! $self->selectedRoom->sourceCodePath
                    && ! $self->selectedRoom->virtualAreaPath
                ) {
                    $item_viewSource->set_sensitive(FALSE);
                }

                my $item_editSource = Gtk3::MenuItem->new('Edit so_urce file...');
                $item_editSource->signal_connect('activate' => sub {

                    if ($self->selectedRoom) {

                        if (! $self->selectedRoom->virtualAreaPath) {

                            # Edit source code file
                            $self->editFileCallback();

                        } else {

                            # Edit virtual area file
                            $self->editFileCallback(TRUE);
                        }
                    }
                });
                $subSubMenu_sourceCode->append($item_editSource);
                # (Also requires either $self->selectedRoom->sourceCodePath or
                #   $self->selectedRoom->virtualAreaPath)
                if (
                    ! $self->selectedRoom->sourceCodePath
                    && ! $self->selectedRoom->virtualAreaPath
                ) {
                    $item_editSource->set_sensitive(FALSE);
                }

            my $item_sourceCode = Gtk3::MenuItem->new('Source _code');
            $item_sourceCode->set_submenu($subSubMenu_sourceCode);
            $subMenu_roomFeatures->append($item_sourceCode);

            $subMenu_roomFeatures->append(Gtk3::SeparatorMenuItem->new());  # Separator

            my $item_setInteriorOffsets = Gtk3::MenuItem->new('_Synchronise grid coordinates...');
            $item_setInteriorOffsets->signal_connect('activate' => sub {

                $self->setInteriorOffsetsCallback();
            });
            $subMenu_roomFeatures->append($item_setInteriorOffsets);

            my $item_resetInteriorOffsets = Gtk3::MenuItem->new('_Reset grid coordinates');
            $item_resetInteriorOffsets->signal_connect('activate' => sub {

                $self->resetInteriorOffsetsCallback();
            });
            $subMenu_roomFeatures->append($item_resetInteriorOffsets);

        my $item_roomFeatures = Gtk3::MenuItem->new('Ot_her room features');
        $item_roomFeatures->set_submenu($subMenu_roomFeatures);
        $menu_rooms->append($item_roomFeatures);

        $menu_rooms->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_deleteRoom = Gtk3::ImageMenuItem->new('_Delete room');
        my $img_deleteRoom = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_deleteRoom->set_image($img_deleteRoom);
        $item_deleteRoom->signal_connect('activate' => sub {

            $self->deleteRoomsCallback();
        });
        $menu_rooms->append($item_deleteRoom);

        # Setup complete
        $menu_rooms->show_all();

        return $menu_rooms;
    }

    sub enableRoomTagsPopupMenu {

        # Called by $self->canvasObjEventHandler
        # Creates a popup-menu for the selected room tag
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->enableRoomTagsPopupMenu',
                @_,
            );
        }

        # Set up the popup menu
        my $menu_tags = Gtk3::Menu->new();
        if (! $menu_tags) {

            return undef;
        }

        # (Everything here assumes $self->currentRegionmap and $self->selectedRoomTag)

        my $item_editTag = Gtk3::MenuItem->new('_Set room tag...');
        $item_editTag->signal_connect('activate' => sub {

            $self->setRoomTagCallback();
        });
        $menu_tags->append($item_editTag);

        my $item_resetPosition = Gtk3::MenuItem->new('_Reset position');
        $item_resetPosition->signal_connect('activate' => sub {

            if ($self->selectedRoomTag) {

                $self->worldModelObj->resetRoomOffsets(
                    TRUE,                       # Update Automapper windows now
                    1,                          # Mode 1 - reset room tag only
                    $self->selectedRoomTag,     # Set to the parent room's blessed reference
                );
            }
        });
        $menu_tags->append($item_resetPosition);

        # Setup complete
        $menu_tags->show_all();

        return $menu_tags;
    }

    sub enableRoomGuildsPopupMenu {

        # Called by $self->canvasObjEventHandler
        # Creates a popup-menu for the selected room guild
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->enableRoomGuildsPopupMenu',
                @_,
            );
        }

        # Set up the popup menu
        my $menu_guilds = Gtk3::Menu->new();
        if (! $menu_guilds) {

            return undef;
        }

        # (Everything here assumes $self->currentRegionmap and $self->selectedRoomGuild)

        my $item_editGuild = Gtk3::MenuItem->new('_Set room guild...');
        $item_editGuild->signal_connect('activate' => sub {

            $self->setRoomGuildCallback();
        });
        $menu_guilds->append($item_editGuild);

        my $item_resetPosition = Gtk3::MenuItem->new('_Reset position');
        $item_resetPosition->signal_connect('activate' => sub {

            if ($self->selectedRoomGuild) {

                $self->worldModelObj->resetRoomOffsets(
                    TRUE,                       # Update Automapper windows now
                    2,                          # Mode 2 - reset room guild only
                    $self->selectedRoomGuild,   # Set to the parent room's blessed reference
                );
            }
        });
        $menu_guilds->append($item_resetPosition);

        # Setup complete
        $menu_guilds->show_all();

        return $menu_guilds;
    }

    sub enableExitsPopupMenu {

        # Called by $self->canvasObjEventHandler
        # Creates a popup-menu for the selected exit
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Local variables
        my @titleList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableExitsPopupMenu', @_);
        }

        # Set up the popup menu
        my $menu_exits = Gtk3::Menu->new();
        if (! $menu_exits) {

            return undef;
        }

        # (Everything here assumes $self->currentRegionmap and $self->selectedExit)

            # 'Allocate map direction' submenu
            my $subMenu_setDir = Gtk3::Menu->new();

            my $item_changeDir = Gtk3::MenuItem->new('_Change direction...');
            $item_changeDir->signal_connect('activate' => sub {

                $self->changeDirCallback();
            });
            $subMenu_setDir->append($item_changeDir);
            # (Also requires $self->selectedExit->drawMode is 'primary' or 'perm_alloc'
            if (
                $self->selectedExit->drawMode ne 'primary'
                && $self->selectedExit->drawMode ne 'perm_alloc'
            ) {
                $item_changeDir->set_sensitive(FALSE);
            }

            my $item_altDir = Gtk3::MenuItem->new('Set _alternative direction(s)...');
            $item_altDir->signal_connect('activate' => sub {

                $self->setAltDirCallback();
            });
            $subMenu_setDir->append($item_altDir);

        my $item_setDir = Gtk3::MenuItem->new('Set di_rection');
        $item_setDir->set_submenu($subMenu_setDir);
        $menu_exits->append($item_setDir);

        my $item_setAssisted = Gtk3::MenuItem->new('Set assisted _move...');
        $item_setAssisted->signal_connect('activate' => sub {

            $self->setAssistedMoveCallback();
        });
        $menu_exits->append($item_setAssisted);
        # (Also requires $self->selectedExit->drawMode 'primary', 'temp_unalloc' or 'perm_unalloc')
        if ($self->selectedExit->drawMode eq 'temp_alloc') {

            $item_setAssisted->set_sensitive(FALSE);
        }

            # 'Allocate map direction' submenu
            my $subMenu_allocateMapDir = Gtk3::Menu->new();

            my $item_allocatePrimary = Gtk3::MenuItem->new('Choose _direction...');
            $item_allocatePrimary->signal_connect('activate' => sub {

                $self->allocateMapDirCallback();
            });
            $subMenu_allocateMapDir->append($item_allocatePrimary);


            my $item_confirmTwoWay = Gtk3::MenuItem->new('Confirm _two-way exit...');
            $item_confirmTwoWay->signal_connect('activate' => sub {

                $self->confirmTwoWayCallback();
            });
            $subMenu_allocateMapDir->append($item_confirmTwoWay);

        my $item_allocateMapDir = Gtk3::MenuItem->new('_Allocate map direction...');
        $item_allocateMapDir->set_submenu($subMenu_allocateMapDir);
        $menu_exits->append($item_allocateMapDir);
        # (Also requires $self->selectedExit->drawMode is 'temp_alloc' or 'temp_unalloc')
        if (
            $self->selectedExit->drawMode ne 'temp_alloc'
            && $self->selectedExit->drawMode ne 'temp_unalloc'
        ) {
            $item_allocateMapDir->set_sensitive(FALSE);
        }

        my $item_allocateShadow = Gtk3::MenuItem->new('Allocate _shadow...');
        $item_allocateShadow->signal_connect('activate' => sub {

            $self->allocateShadowCallback();
        });
        $menu_exits->append($item_allocateShadow);
        # (Also requires $self->selectedExit->drawMode is 'temp_alloc' or 'temp_unalloc')
        if (
            $self->selectedExit->drawMode ne 'temp_alloc'
            && $self->selectedExit->drawMode ne 'temp_unalloc'
        ) {
            $item_allocateShadow->set_sensitive(FALSE);
        }

        $menu_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_connectExitToClick = Gtk3::MenuItem->new('_Connect to click');
        $item_connectExitToClick->signal_connect('activate' => sub {

            $self->connectToClickCallback();
        });
        $menu_exits->append($item_connectExitToClick);
        # (Also requires $self->selectedExit->drawMode 'primary', 'temp_unalloc' or 'perm_unalloc')
        if ($self->selectedExit->drawMode eq 'temp_alloc') {

            $item_connectExitToClick->set_sensitive(FALSE);
        }

        my $item_disconnectExit = Gtk3::MenuItem->new('D_isconnect exit');
        $item_disconnectExit->signal_connect('activate' => sub {

            $self->disconnectExitCallback();
        });
        $menu_exits->append($item_disconnectExit);

        $menu_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_addExitBend = Gtk3::MenuItem->new('Add _bend');
        $item_addExitBend->signal_connect('activate' => sub {

            $self->addBendCallback();
        });
        $menu_exits->append($item_addExitBend);
        # (Also requires a $self->selectedExit that's a one-way or two-way broken exit, not a region
        #   exit, and also defined values for $self->exitClickXPosn and $self->exitClickYPosn)
        if (
            (! $self->selectedExit->oneWayFlag && ! $self->selectedExit->twinExit)
            || $self->selectedExit->regionFlag
            || ! defined $self->exitClickXPosn
            || ! defined $self->exitClickYPosn
        ) {
            $item_addExitBend->set_sensitive(FALSE);
        }

        my $item_removeExitBend = Gtk3::MenuItem->new('Remo_ve bend');
        $item_removeExitBend->signal_connect('activate' => sub {

            $self->removeBendCallback();
        });
        $menu_exits->append($item_removeExitBend);
        # (Also requires a $self->selectedExit that's a one-way or two-way exit with a bend, and
        #   also defined values for $self->exitClickXPosn and $self->exitClickYPosn)
        if (
            (! $self->selectedExit->oneWayFlag && ! $self->selectedExit->twinExit)
            || ! $self->selectedExit->bendOffsetList
            || ! defined $self->exitClickXPosn
            || ! defined $self->exitClickYPosn
        ) {
            $item_removeExitBend->set_sensitive(FALSE);
        }

        $menu_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Set ornaments' submenu
            my $subMenu_setOrnament = Gtk3::Menu->new();

            # Create a list of exit ornament types, in groups of two, in the form
            #   (menu_item_title, exit_ornament_type)
            @titleList = (
                '_No ornament', 'none',
                '_Openable exit', 'open',
                '_Lockable exit', 'lock',
                '_Pickable exit', 'pick',
                '_Breakable exit', 'break',
                '_Impassable exit', 'impass',
                '_Mystery exit', 'mystery',
            );

            do {

                my ($title, $type);

                $title = shift @titleList;
                $type = shift @titleList;

                my $menuItem = Gtk3::MenuItem->new($title);
                $menuItem->signal_connect('activate' => sub {

                    $self->exitOrnamentCallback($type);
                });
                $subMenu_setOrnament->append($menuItem);

            } until (! @titleList);

            $subMenu_setOrnament->append(Gtk3::SeparatorMenuItem->new());   # Separator

            my $item_setTwinOrnament = Gtk3::CheckMenuItem->new('Also set _twin exits');
            $item_setTwinOrnament->set_active($self->worldModelObj->setTwinOrnamentFlag);
            $item_setTwinOrnament->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFlag(
                        'setTwinOrnamentFlag',
                        $item_setTwinOrnament->get_active(),
                        FALSE,      # Don't call $self->redrawRegions
                        'also_set_twin_exits',
                    );
                }
            });
            $subMenu_setOrnament->append($item_setTwinOrnament);

        my $item_setOrnament = Gtk3::MenuItem->new('Set _ornaments');
        $item_setOrnament->set_submenu($subMenu_setOrnament);
        $menu_exits->append($item_setOrnament);

            # 'Set exit type' submenu
            my $subMenu_setExitType = Gtk3::Menu->new();

                # 'Set hidden' sub-submenu
                my $subSubMenu_setHidden = Gtk3::Menu->new();

                my $item_setHiddenExit = Gtk3::MenuItem->new('Mark exit _hidden');
                $item_setHiddenExit->signal_connect('activate' => sub {

                    $self->hiddenExitCallback(TRUE);
                });
                $subSubMenu_setHidden->append($item_setHiddenExit);

                my $item_setNotHiddenExit = Gtk3::MenuItem->new('Mark exit _not hidden');
                $item_setNotHiddenExit->signal_connect('activate' => sub {

                    $self->hiddenExitCallback(FALSE);
                });
                $subSubMenu_setHidden->append($item_setNotHiddenExit);

            my $item_setHidden = Gtk3::MenuItem->new('Set _hidden');
            $item_setHidden->set_submenu($subSubMenu_setHidden);
            $subMenu_setExitType->append($item_setHidden);

                # 'Set broken' sub-submenu
                my $subSubMenu_setBroken = Gtk3::Menu->new();

                my $item_markBrokenExit = Gtk3::MenuItem->new('_Mark exit as broken');
                $item_markBrokenExit->signal_connect('activate' => sub {

                    $self->markBrokenExitCallback();
                });
                $subSubMenu_setBroken->append($item_markBrokenExit);

                my $item_toggleBrokenExit = Gtk3::MenuItem->new('_Toggle bent broken exit');
                $item_toggleBrokenExit->signal_connect('activate' => sub {

                    $self->worldModelObj->toggleBentExit(
                        TRUE,                       # Update Automapper windows now
                        $self->selectedExit,
                    );
                });
                $subSubMenu_setBroken->append($item_toggleBrokenExit);
                # (Also requires $self->selectedExit->brokenFlag)
                if (! $self->selectedExit->brokenFlag) {

                    $item_toggleBrokenExit->set_sensitive(FALSE);
                }

                $subSubMenu_setBroken->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_restoreBrokenExit = Gtk3::MenuItem->new('_Restore unbroken exit');
                $item_restoreBrokenExit->signal_connect('activate' => sub {

                    $self->restoreBrokenExitCallback();
                });
                $subSubMenu_setBroken->append($item_restoreBrokenExit);

            my $item_setBroken = Gtk3::MenuItem->new('Set _broken');
            $item_setBroken->set_submenu($subSubMenu_setBroken);
            $subMenu_setExitType->append($item_setBroken);

                # 'Set one-way' sub-submenu
                my $subSubMenu_setOneWay = Gtk3::Menu->new();

                my $item_markOneWayExit = Gtk3::MenuItem->new('_Mark exit as one-way');
                $item_markOneWayExit->signal_connect('activate' => sub {

                    $self->markOneWayExitCallback();
                });
                $subSubMenu_setOneWay->append($item_markOneWayExit);

                $subSubMenu_setOneWay->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_restoreUncertainExit = Gtk3::MenuItem->new('Restore _uncertain exit');
                $item_restoreUncertainExit->signal_connect('activate' => sub {

                    $self->restoreOneWayExitCallback(FALSE);
                });
                $subSubMenu_setOneWay->append($item_restoreUncertainExit);

                my $item_restoreTwoWayExit = Gtk3::MenuItem->new('Restore _two-way exit');
                $item_restoreTwoWayExit->signal_connect('activate' => sub {

                    $self->restoreOneWayExitCallback(TRUE);
                });
                $subSubMenu_setOneWay->append($item_restoreTwoWayExit);

                $subSubMenu_setOneWay->append(Gtk3::SeparatorMenuItem->new());  # Separator

                my $item_setIncomingDir = Gtk3::MenuItem->new('Set incoming _direction...');
                $item_setIncomingDir->signal_connect('activate' => sub {

                    $self->setIncomingDirCallback();
                });
                $subSubMenu_setOneWay->append($item_setIncomingDir);
                # (Also requires either a $self->selectedExit which is a one-way exit)
                if (! $self->selectedExit->oneWayFlag) {

                    $item_setIncomingDir->set_sensitive(FALSE);
                }

            my $item_setOneWay = Gtk3::MenuItem->new('Set _one-way');
            $item_setOneWay->set_submenu($subSubMenu_setOneWay);
            $subMenu_setExitType->append($item_setOneWay);

                # 'Set retracing' sub-submenu
                my $subSubMenu_setRetracing = Gtk3::Menu->new();

                my $item_markRetracingExit = Gtk3::MenuItem->new('_Mark exit as retracing');
                $item_markRetracingExit->signal_connect('activate' => sub {

                    $self->markRetracingExitCallback();
                });
                $subSubMenu_setRetracing->append($item_markRetracingExit);

                $subSubMenu_setRetracing->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_restoreRetracingExit = Gtk3::MenuItem->new('_Restore incomplete exit');
                $item_restoreRetracingExit->signal_connect('activate' => sub {

                    $self->restoreRetracingExitCallback();
                });
                $subSubMenu_setRetracing->append($item_restoreRetracingExit);

            my $item_setRetracing = Gtk3::MenuItem->new('Set _retracing');
            $item_setRetracing->set_submenu($subSubMenu_setRetracing);
            $subMenu_setExitType->append($item_setRetracing);

                # 'Set random' sub-submenu
                my $subSubMenu_setRandomExit = Gtk3::Menu->new();

                my $item_markRandomRegion = Gtk3::MenuItem->new(
                    'Set random destination in same _region',
                );
                $item_markRandomRegion->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('same_region');
                });
                $subSubMenu_setRandomExit->append($item_markRandomRegion);

                my $item_markRandomAnywhere = Gtk3::MenuItem->new(
                    'Set random destination _anywhere',
                );
                $item_markRandomAnywhere->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('any_region');
                });
                $subSubMenu_setRandomExit->append($item_markRandomAnywhere);

                my $item_randomTempRegion = Gtk3::MenuItem->new(
                    '_Create destination in temporary region',
                );
                $item_randomTempRegion->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('temp_region');
                });
                $subSubMenu_setRandomExit->append($item_randomTempRegion);

                my $item_markRandomList = Gtk3::MenuItem->new('_Use list of random destinations');
                $item_markRandomList->signal_connect('activate' => sub {

                    $self->markRandomExitCallback('room_list');
                });
                $subSubMenu_setRandomExit->append($item_markRandomList);

                $subSubMenu_setRandomExit->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_restoreRandomExit = Gtk3::MenuItem->new('Restore _incomplete exit');
                $item_restoreRandomExit->signal_connect('activate' => sub {

                    $self->restoreRandomExitCallback();
                });
                $subSubMenu_setRandomExit->append($item_restoreRandomExit);

            my $item_setRandomExit = Gtk3::MenuItem->new('Set r_andom');
            $item_setRandomExit->set_submenu($subSubMenu_setRandomExit);
            $subMenu_setExitType->append($item_setRandomExit);

                # 'Set super' sub-submenu
                my $subSubMenu_setSuperExit = Gtk3::Menu->new();

                my $item_markSuper = Gtk3::MenuItem->new('Mark exit as _super-region exit');
                $item_markSuper->signal_connect('activate' => sub {

                    $self->markSuperExitCallback(FALSE);
                });
                $subSubMenu_setSuperExit->append($item_markSuper);

                my $item_markSuperExcl = Gtk3::MenuItem->new(
                    'Mark exit as _exclusive super-region exit',
                );
                $item_markSuperExcl->signal_connect('activate' => sub {

                    $self->markSuperExitCallback(TRUE);
                });
                $subSubMenu_setSuperExit->append($item_markSuperExcl);

                $subSubMenu_setSuperExit->append(Gtk3::SeparatorMenuItem->new());    # Separator

                my $item_markNotSuper = Gtk3::MenuItem->new('Mark exit as _normal region exit');
                $item_markNotSuper->signal_connect('activate' => sub {

                    $self->restoreSuperExitCallback();
                });
                $subSubMenu_setSuperExit->append($item_markNotSuper);

            my $item_setSuperExit = Gtk3::MenuItem->new('Set _super');
            $item_setSuperExit->set_submenu($subSubMenu_setSuperExit);
            $subMenu_setExitType->append($item_setSuperExit);
            # (Also requires $self->selectedExit->regionFlag)
            if (! $self->selectedExit->regionFlag) {

                $item_setSuperExit->set_sensitive(FALSE);
            }

            $subMenu_setExitType->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_setExitTwin = Gtk3::MenuItem->new('Set exit _twin...');
            $item_setExitTwin->signal_connect('activate' => sub {

                $self->setExitTwinCallback();
            });
            $subMenu_setExitType->append($item_setExitTwin);
            # (Also requires either a $self->selectedExit which is either a one-way exit or an
            #   uncertain exit)
            if (
                ! $self->selectedExit->oneWayFlag
                || ! (
                    $self->selectedExit->destRoom
                    && ! $self->selectedExit->twinExit
                    && ! $self->selectedExit->retraceFlag
                    && $self->selectedExit->randomType eq 'none'
                )
            ) {
                $item_setExitTwin->set_sensitive(FALSE);
            }

        my $item_setExitType = Gtk3::MenuItem->new('Set _exit type');
        $item_setExitType->set_submenu($subMenu_setExitType);
        $menu_exits->append($item_setExitType);

            # 'Exit tags' submenu
            my $subMenu_exitTags = Gtk3::Menu->new();

            my $item_editTag = Gtk3::MenuItem->new('_Edit exit tag');
            $item_editTag->signal_connect('activate' => sub {

                $self->editExitTagCallback();
            });
            $subMenu_exitTags->append($item_editTag);

            my $item_toggleExitTag = Gtk3::MenuItem->new('_Toggle exit tag');
            $item_toggleExitTag->signal_connect('activate' => sub {

                $self->toggleExitTagCallback();
            });
            $subMenu_exitTags->append($item_toggleExitTag);

            $subMenu_exitTags->append(Gtk3::SeparatorMenuItem->new());    # Separator

            my $item_resetPosition = Gtk3::MenuItem->new('_Reset text position');
            $item_resetPosition->signal_connect('activate' => sub {

                $self->resetExitOffsetsCallback();
            });
            $subMenu_exitTags->append($item_resetPosition);

        my $item_exitTags = Gtk3::MenuItem->new('Exit _tags');
        $item_exitTags->set_submenu($subMenu_exitTags);
        $menu_exits->append($item_exitTags);
        # (Also requires either a $self->selectedExit which is a region exit)
        if (! $self->selectedExit->regionFlag) {

            $item_exitTags->set_sensitive(FALSE);
        }

        $menu_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_editExit = Gtk3::ImageMenuItem->new('Edit e_xit...');
        my $img_editExit = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_editExit->set_image($img_editExit);
        $item_editExit->signal_connect('activate' => sub {

            $self->editExitCallback();
        });
        $menu_exits->append($item_editExit);

        $menu_exits->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_deleteExit = Gtk3::ImageMenuItem->new('_Delete exit');
        my $img_deleteExit = Gtk3::Image->new_from_stock('gtk-add', 'menu');
        $item_deleteExit->set_image($img_deleteExit);
        $item_deleteExit->signal_connect('activate' => sub {

            $self->deleteExitCallback();
        });
        $menu_exits->append($item_deleteExit);

        # Setup complete
        $menu_exits->show_all();

        return $menu_exits;
    }

    sub enableExitTagsPopupMenu {

        # Called by $self->canvasObjEventHandler
        # Creates a popup-menu for the selected exit tag
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->enableExitTagsPopupMenu',
                @_,
            );
        }

        # Set up the popup menu
        my $menu_tags = Gtk3::Menu->new();
        if (! $menu_tags) {

            return undef;
        }

        # (Everything here assumes $self->currentRegionmap and $self->selectedExitTag)

        my $item_editTag = Gtk3::MenuItem->new('_Edit exit tag');
        $item_editTag->signal_connect('activate' => sub {

            $self->editExitTagCallback();
        });
        $menu_tags->append($item_editTag);

        my $item_cancelTag = Gtk3::MenuItem->new('_Cancel exit tag');
        $item_cancelTag->signal_connect('activate' => sub {

            $self->toggleExitTagCallback();
        });
        $menu_tags->append($item_cancelTag);

        $menu_tags->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_viewDestination = Gtk3::MenuItem->new('_View destination');
        $item_viewDestination->signal_connect('activate' => sub {

            $self->viewExitDestination();
        });
        $menu_tags->append($item_viewDestination);

        $menu_tags->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_resetPosition = Gtk3::MenuItem->new('_Reset position');
        $item_resetPosition->signal_connect('activate' => sub {

            $self->resetExitOffsetsCallback();
        });
        $menu_tags->append($item_resetPosition);

        # Setup complete
        $menu_tags->show_all();

        return $menu_tags;
    }

    sub enableLabelsPopupMenu {

        # Called by $self->canvasObjEventHandler
        # Creates a popup-menu for the selected label
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::Menu created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableLabelsPopupMenu', @_);
        }

        # Set up the popup menu
        my $menu_labels = Gtk3::Menu->new();
        if (! $menu_labels) {

            return undef;
        }

        # (Everything here assumes $self->currentRegionmap and $self->selectedLabel)

        my $item_setLabel = Gtk3::ImageMenuItem->new('_Set label...');
        my $img_setLabel = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_setLabel->set_image($img_setLabel);
        $item_setLabel->signal_connect('activate' => sub {

            $self->setLabelCallback(FALSE)
        });
        $menu_labels->append($item_setLabel);

        my $item_customiseLabel = Gtk3::ImageMenuItem->new('_Customise label...');
        my $img_customiseLabel = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
        $item_customiseLabel->set_image($img_customiseLabel);
        $item_customiseLabel->signal_connect('activate' => sub {

            $self->setLabelCallback(TRUE);
        });
        $menu_labels->append($item_customiseLabel);

        $menu_labels->append(Gtk3::SeparatorMenuItem->new());  # Separator

            # 'Set label style' submenu
            my $subMenu_setStyle = Gtk3::Menu->new();

            foreach my $style (
                sort {lc($a) cmp lc($b)} ($self->worldModelObj->ivKeys('mapLabelStyleHash'))
            ) {
                my $item_thisStyle = Gtk3::MenuItem->new($style);
                $item_thisStyle->signal_connect('activate' => sub {

                    $self->setLabelDirectCallback($style);
                });
                $subMenu_setStyle->append($item_thisStyle);
            }

        my $item_setStyle = Gtk3::MenuItem->new('S_et label style');
        $item_setStyle->set_submenu($subMenu_setStyle);
        $menu_labels->append($item_setStyle);
        # (Also requires at least one label style)
        if (! $self->worldModelObj->mapLabelStyleHash) {

            $item_setStyle->set_sensitive(FALSE);
        }

        $menu_labels->append(Gtk3::SeparatorMenuItem->new());  # Separator

        my $item_deleteLabel = Gtk3::ImageMenuItem->new('_Delete label');
        my $img_deleteLabel = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_deleteLabel->set_image($img_deleteLabel);
        $item_deleteLabel->signal_connect('activate' => sub {

            if ($self->selectedLabel) {

                $self->worldModelObj->deleteLabels(
                    TRUE,           # Update Automapper windows now
                    $self->selectedLabel,
                );
            }
        });
        $menu_labels->append($item_deleteLabel);

        my $item_quickDelete = Gtk3::ImageMenuItem->new('_Quick label deletion...');
        my $img_quickDelete = Gtk3::Image->new_from_stock('gtk-delete', 'menu');
        $item_quickDelete->set_image($img_quickDelete);
        $item_quickDelete->signal_connect('activate' => sub {

            $self->session->pseudoCmd('quicklabeldelete', $self->pseudoCmdMode);
        });
        $menu_labels->append($item_quickDelete);

        # Setup complete
        $menu_labels->show_all();

        return $menu_labels;
    }

    # Toolbar widget methods

    sub enableToolbar {

        # Called by $self->drawWidgets
        # Sets up the Automapper window's Gtk3::Toolbar widget(s)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if one of the widgets can't be created
        #   Otherwise returns a list of Gtk3::Toolbar widgets created

        my ($self, $check) = @_;

        # Local variables
        my (
            $flag,
            @emptyList, @setList, @widgetList,
            %checkHash,
        );

        # Check for improper arguments
        if (defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableToolbar', @_);
            return @emptyList;
        }

        # Import the list of button sets from the world model
        # Remove 'default' (which shouldn't be there) and also remove any duplicates or unrecognised
        #   sets
        foreach my $set ($self->worldModelObj->buttonSetList) {

            if (
                $set ne $self->constToolbarDefaultSet
                && ! exists $checkHash{$set}
                && $self->ivExists('buttonSetHash', $set)
            ) {
                push (@setList, $set);
                # Watch out for duplicates
                $checkHash{$set} = undef;
            }
        }

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        $self->ivAdd('buttonSetHash', $nextSet, TRUE);
        $self->ivAdd('toolbarHash', $toolbar, $nextSet);
        $self->ivPoke('toolbarButtonList', @buttonList);
        $self->ivPoke('toolbarOriginalSet', $nextSet);

        # Not worth calling $self->redrawWidgets, so must do a ->show_all()
        $toolbar->show_all();

        return 1;
    }

    sub addToolbar {

        # Called by a ->signal_connect in $self->drawToolbar whenever the user clicks the original
        #   toolbar's add button
        # Creates a popup menu containing all of the button sets that aren't currently visible, then
        #   imlements the user's choice
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if there's an error
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            @list,
            %hash,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->addToolbar', @_);
        }

        # Get a list of button sets that aren't already visible
        # NB The 'default' set can only be viewed in the original (first) toolbar, so it's not added
        #   to this list
        foreach my $set ($self->constButtonSetList) {

            my $descrip = $self->ivShow('constButtonDescripHash', $set);

            if ($set ne $self->constToolbarDefaultSet && ! $self->ivShow('buttonSetHash', $set)) {

                push (@list, $descrip);
                $hash{$descrip} = $set;
            }
        }

        if (! @list) {

            # All button sets are visible (this shouldn't happen)
            return undef;
        }

        # Set up the popup menu
        my $popupMenu = Gtk3::Menu->new();
        if (! $popupMenu) {

            return undef;
        }

        # Add a title menu item, which does nothing
        my $title_item = Gtk3::MenuItem->new('Add button set:');
        $title_item->signal_connect('activate' => sub {

            return undef;
        });
        $title_item->set_sensitive(FALSE);
        $popupMenu->append($title_item);

        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        # Fill the popup menu with button sets
        foreach my $descrip (@list) {

            my $menu_item = Gtk3::MenuItem->new($descrip);
            $menu_item->signal_connect('activate' => sub {

                # Add the set to the world model's list of button sets...
                $self->worldModelObj->add_buttonSet($hash{$descrip});
                # ...then redraw the window component containing the toolbar(s)
                $self->redrawWidgets('toolbar');
            });
            $popupMenu->append($menu_item);
        }

        # Also add a 'Cancel' menu item, which does nothing
        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        my $cancel_item = Gtk3::MenuItem->new('Cancel');
        $cancel_item->signal_connect('activate' => sub {

            return undef;
        });
        $popupMenu->append($cancel_item);

        # Display the popup menu
        $popupMenu->popup(
            undef, undef, undef, undef,
            1,                              # Left mouse button
            Gtk3::get_current_event_time(),
        );

        $popupMenu->show_all();

        # Operation complete. Now wait for the user's response
        return 1;
    }

    sub removeToolbar {

        # Called by a ->signal_connect in $self->drawToolbar whenever the user clicks on the remove
        #   button in any toolbar except the original one
        # Removes the specified toolbar and updates IVs
        #
        # Expected arguments
        #   $toolbar    - The toolbar widget to be removed
        #
        # Return values
        #   'undef' on improper arguments or if there's an error
        #   1 otherwise

        my ($self, $toolbar, $check) = @_;

        # Local variables
        my (
            $set,
            @modList,
        );

        # Check for improper arguments
        if (! defined $toolbar || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->removeToolbar', @_);
        }

        # Check the toolbar widget still exists (no reason it shouldn't, but it doesn't hurt to
        #   check)
        if (! $self->ivExists('toolbarHash', $toolbar)) {

            return undef;

        } else {

            # Get the button set that was drawn in this toolbar
            $set = $self->ivShow('toolbarHash', $toolbar);
        }

        # Add the set to the world model's list of button sets...
        $self->worldModelObj->del_buttonSet($set);
        # ...then redraw the window component containing the toolbar(s)
        $self->redrawWidgets('toolbar');

        return 1;
    }

    sub chooseButtonSet {

        # Called by $self->drawToolbar and ->switchToolbarButtons
        # Calls the right function for the specified button set, and returns the result
        #
        # Expected arguments
        #   $toolbar    - The toolbar widget on which the buttons are drawn
        #   $set        - The button set to use (one of the items in $self->constButtonSetList)

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

            return $self->drawDefaultButtonSet($toolbar);
        } elsif ($set eq 'exits') {
            return $self->drawExitsButtonSet($toolbar);
        } elsif ($set eq 'painting') {
            return $self->drawPaintingButtonSet($toolbar);
        } elsif ($set eq 'quick') {
            return $self->drawQuickButtonSet($toolbar);
        } elsif ($set eq 'background') {
            return $self->drawBackgroundButtonSet($toolbar);
        } elsif ($set eq 'tracking') {
            return $self->drawTrackingButtonSet($toolbar);
        } elsif ($set eq 'misc') {
            return $self->drawMiscButtonSet($toolbar);
        } elsif ($set eq 'flags') {
            return $self->drawFlagsButtonSet($toolbar);
        } elsif ($set eq 'interiors') {
            return $self->drawInteriorsButtonSet($toolbar);
        } else {
            return @emptyList;
        }
    }

    sub drawDefaultButtonSet {

        # Called by $self->chooseButtonSet, which in turn was called by $self->drawToolbar or
        #   ->switchToolbarButtons
        # Draws buttons for this button set, and adds them to the toolbar
        #
        # Expected arguments
        #   $toolbar    - The toolbar widget on which the buttons are drawn
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $toolbar, $check) = @_;

        # Local variables
        my @buttonList;

        # Check for improper arguments
        if (! defined $toolbar || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->drawDefaultButtonSet', @_);
        }

        # Radio button for 'wait mode'
        my $radioButton_waitMode = Gtk3::RadioToolButton->new(undef);
        if ($self->mode eq 'wait') {

            $radioButton_waitMode->set_active(TRUE);
        }
        $radioButton_waitMode->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_wait.png')
        );
        $radioButton_waitMode->set_label('Wait mode');
        $radioButton_waitMode->set_tooltip_text('Wait mode');
        $radioButton_waitMode->signal_connect('toggled' => sub {

            # (To stop the equivalent menu item from being toggled by the call to ->setMode, make
            #   use of $self->ignoreMenuUpdateFlag)
            if ($radioButton_waitMode->get_active && ! $self->ignoreMenuUpdateFlag) {

                $self->setMode('wait');
            }
        });
        push (@buttonList, $radioButton_waitMode);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_set_wait_mode', $radioButton_waitMode);

        # Radio button for 'follow mode'
        my $radioButton_followMode = Gtk3::RadioToolButton->new_from_widget($radioButton_waitMode);
        if ($self->mode eq 'follow') {

            $radioButton_followMode->set_active(TRUE);
        }
        $radioButton_followMode->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_follow.png')
        );
        $radioButton_followMode->set_label('Follow mode');
        $radioButton_followMode->set_tooltip_text('Follow mode');
        $radioButton_followMode->signal_connect('toggled' => sub {

            if ($radioButton_followMode->get_active && ! $self->ignoreMenuUpdateFlag) {

                $self->setMode('follow');
            }
        });
        push (@buttonList, $radioButton_followMode);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'icon_set_follow_mode', $radioButton_followMode);

        # Radio button for 'update' mode
        my $radioButton_updateMode = Gtk3::RadioToolButton->new_from_widget(
            $radioButton_followMode,
        );
        if ($self->mode eq 'update') {

            $radioButton_updateMode->set_active(TRUE);
        }
        $radioButton_updateMode->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_update.png')
        );
        $radioButton_updateMode->set_label('Update mode');
        $radioButton_updateMode->set_tooltip_text('Update mode');
        $radioButton_updateMode->signal_connect('toggled' => sub {

            if ($radioButton_updateMode->get_active && ! $self->ignoreMenuUpdateFlag) {

                $self->setMode('update');
            }
        });
        push (@buttonList, $radioButton_updateMode);
        # (Requires $self->currentRegionmap, GA::Obj::WorldModel->disableUpdateModeFlag set to
        #   FALSE and a session not in 'connect offline' mode
        $self->ivAdd('menuToolItemHash', 'icon_set_update_mode', $radioButton_updateMode);

        # Separator
        my $separator = Gtk3::SeparatorToolItem->new();
        push (@buttonList, $separator);

        # Toolbutton for 'move up level'
        my $toolButton_moveUpLevel = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_move_up.png'),
            'Move up level',
        );
        $toolButton_moveUpLevel->set_tooltip_text('Move up level');
        $toolButton_moveUpLevel->signal_connect('clicked' => sub {

            $self->setCurrentLevel($self->currentRegionmap->currentLevel + 1);

            # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
            $self->restrictWidgets();
        });
        push (@buttonList, $toolButton_moveUpLevel);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'icon_move_up_level', $toolButton_moveUpLevel);

        # Toolbutton for 'move down level'
        my $toolButton_moveDownLevel = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_move_down.png'),
            'Move down level',
        );
        $toolButton_moveDownLevel->set_tooltip_text('Move down level');
        $toolButton_moveDownLevel->signal_connect('clicked' => sub {

            $self->setCurrentLevel($self->currentRegionmap->currentLevel - 1);

            # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
            $self->restrictWidgets();
        });
        push (@buttonList, $toolButton_moveDownLevel);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'icon_move_down_level', $toolButton_moveDownLevel);

        # Separator
        my $separator2 = Gtk3::SeparatorToolItem->new();
        push (@buttonList, $separator2);

        # Toolbutton for 'reset locator'
        my $toolButton_resetLocator = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_reset_locator.png'),
            'Reset Locator task',
        );
        $toolButton_resetLocator->set_tooltip_text('Reset locator task');
        $toolButton_resetLocator->signal_connect('clicked' => sub {

            $self->resetLocatorCallback();

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        $toolButton_connectClick->set_tooltip_text('Connect selected exit to room');
        $toolButton_connectClick->signal_connect('clicked' => sub {

            $self->connectToClickCallback();
        });
        push (@buttonList, $toolButton_connectClick);
        # (Requires $self->currentRegionmap, $self->selectedExit and
        #   $self->selectedExit->drawMode is 'primary', 'temp_unalloc' or 'perm_alloc')
        $self->ivAdd('menuToolItemHash', 'icon_connect_click', $toolButton_connectClick);

        # Toolbutton for 'take screenshot'
        my $toolButton_visibleScreenshot = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_take_screenshot.png'),
            'Take screenshot of visible map',
        );
        $toolButton_visibleScreenshot->set_tooltip_text('Take screenshot of visible map');
        $toolButton_visibleScreenshot->signal_connect('clicked' => sub {

            $self->regionScreenshotCallback('visible');
        });
        push (@buttonList, $toolButton_visibleScreenshot);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'icon_visible_screenshot', $toolButton_visibleScreenshot);

        return @buttonList;
    }

    sub drawExitsButtonSet {

        # Called by $self->chooseButtonSet, which in turn was called by $self->drawToolbar or
        #   ->switchToolbarButtons
        # Draws buttons for this button set, and adds them to the toolbar
        #
        # Expected arguments
        #   $toolbar    - The toolbar widget on which the buttons are drawn
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $toolbar, $check) = @_;

        # Local variables
        my @buttonList;

        # Check for improper arguments
        if (! defined $toolbar || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->drawExitsButtonSet', @_);
        }

        # Radio button for 'use region exit settings' mode
        my $radioButton_deferDrawExits = Gtk3::RadioToolButton->new(undef);
        $radioButton_deferDrawExits->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_use_region.png'),
        );
        $radioButton_deferDrawExits->set_label('Use region exit settings');
        $radioButton_deferDrawExits->set_tooltip_text('Use region exit settings');
        $radioButton_deferDrawExits->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_deferDrawExits->get_active()) {

                $self->worldModelObj->switchMode(
                    'drawExitMode',
                    'ask_regionmap',    # New value of ->drawExitMode
                    TRUE,               # Do call $self->redrawRegions
                    'draw_defer_exits',
                    'icon_draw_defer_exits',
                );
            }
        });
        push (@buttonList, $radioButton_deferDrawExits);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_draw_defer_exits', $radioButton_deferDrawExits);

        # Radio button for 'draw no exits' mode
        my $radioButton_drawNoExits = Gtk3::RadioToolButton->new_from_widget(
            $radioButton_deferDrawExits,
        );
        if ($self->worldModelObj->drawExitMode eq 'no_exit') {

            $radioButton_drawNoExits->set_active(TRUE);
        }
        $radioButton_drawNoExits->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_draw_none.png'),
        );
        $radioButton_drawNoExits->set_label('Draw no exits');
        $radioButton_drawNoExits->set_tooltip_text('Draw no exits');
        $radioButton_drawNoExits->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_drawNoExits->get_active()) {

                $self->worldModelObj->switchMode(
                    'drawExitMode',
                    'no_exit',          # New value of ->drawExitMode
                    TRUE,               # Do call $self->redrawRegions
                    'draw_no_exits',
                    'icon_draw_no_exits',
                );
            }
        });
        push (@buttonList, $radioButton_drawNoExits);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_draw_no_exits', $radioButton_drawNoExits);

        # Radio button for 'draw simple exits' mode
        my $radioButton_drawSimpleExits = Gtk3::RadioToolButton->new_from_widget(
            $radioButton_drawNoExits,
        );
        if ($self->worldModelObj->drawExitMode eq 'simple_exit') {

            $radioButton_drawSimpleExits->set_active(TRUE);
        }
        $radioButton_drawSimpleExits->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_draw_simple.png'),
        );
        $radioButton_drawSimpleExits->set_label('Draw simple exits');
        $radioButton_drawSimpleExits->set_tooltip_text('Draw simple exits');
        $radioButton_drawSimpleExits->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_drawSimpleExits->get_active()) {

                $self->worldModelObj->switchMode(
                    'drawExitMode',
                    'simple_exit',      # New value of ->drawExitMode
                    TRUE,               # Do call $self->redrawRegions
                    'draw_simple_exits',
                    'icon_draw_simple_exits',
                );
            }
        });
        push (@buttonList, $radioButton_drawSimpleExits);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_draw_simple_exits', $radioButton_drawSimpleExits);

        # Radio button for 'draw complex exits' mode
        my $radioButton_drawComplexExits = Gtk3::RadioToolButton->new_from_widget(
            $radioButton_drawSimpleExits,
        );
        if ($self->worldModelObj->drawExitMode eq 'complex_exit') {

            $radioButton_drawComplexExits->set_active(TRUE);
        }
        $radioButton_drawComplexExits->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_draw_complex.png'),
        );
        $radioButton_drawComplexExits->set_label('Draw complex exits');
        $radioButton_drawComplexExits->set_tooltip_text('Draw complex exits');
        $radioButton_drawComplexExits->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_drawComplexExits->get_active()) {

                $self->worldModelObj->switchMode(
                    'drawExitMode',
                    'complex_exit',     # New value of ->drawExitMode
                    TRUE,               # Do call $self->redrawRegions
                    'draw_complex_exits',
                    'icon_draw_complex_exits',
                );
            }
        });
        push (@buttonList, $radioButton_drawComplexExits);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_draw_complex_exits', $radioButton_drawComplexExits);

        # Toggle button for 'obscure unimportant exits'
        my $toggleButton_obscuredExits = Gtk3::ToggleToolButton->new();
        $toggleButton_obscuredExits->set_active($self->worldModelObj->obscuredExitFlag);
        $toggleButton_obscuredExits->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_obscured_exits.png'),
        );
        $toggleButton_obscuredExits->set_label('Obscure unimportant exits');
        $toggleButton_obscuredExits->set_tooltip_text('Obscure unimportant exits');
        $toggleButton_obscuredExits->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag) {

                $self->worldModelObj->toggleFlag(
                    'obscuredExitFlag',
                    $toggleButton_obscuredExits->get_active(),
                    TRUE,      # Do call $self->redrawRegions
                    'obscured_exits',
                    'icon_obscured_exits',
                );
            }
        });
        push (@buttonList, $toggleButton_obscuredExits);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_obscured_exits', $toggleButton_obscuredExits);

        # Toggle button for 'auto-redraw obscured exits'
        my $toggleButton_autoRedraw = Gtk3::ToggleToolButton->new();
        $toggleButton_autoRedraw->set_active($self->worldModelObj->obscuredExitRedrawFlag);
        $toggleButton_autoRedraw->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_auto_redraw.png'),
        );
        $toggleButton_autoRedraw->set_label('Auto-redraw obscured exits');
        $toggleButton_autoRedraw->set_tooltip_text('Auto-redraw obscured exits');
        $toggleButton_autoRedraw->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag) {

                $self->worldModelObj->toggleFlag(
                    'obscuredExitRedrawFlag',
                    $toggleButton_autoRedraw->get_active(),
                    TRUE,      # Do call $self->redrawRegions
                    'auto_redraw_obscured',
                    'icon_auto_redraw_obscured',
                );
            }
        });
        push (@buttonList, $toggleButton_autoRedraw);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_auto_redraw_obscured', $toggleButton_autoRedraw);

        # Toolbutton for 'obscure exits in radius'
        my $toolButton_obscuredRadius = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_obscured_radius.png'),
            'Obscure exits in radius',
        );
        $toolButton_obscuredRadius->set_tooltip_text('Obscure exits in radius');
        $toolButton_obscuredRadius->signal_connect('clicked' => sub {

            $self->obscuredRadiusCallback();
        });
        push (@buttonList, $toolButton_obscuredRadius);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_obscured_radius', $toolButton_obscuredRadius);

        # Separator
        my $separator = Gtk3::SeparatorToolItem->new();
        push (@buttonList, $separator);

        # Toolbutton for 'horizontal exit length'
        my $toolButton_horizontalLengths = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR
                . '/icons/map/icon_horizontal_lengths.png'),
            'Horizontal exit length',
        );
        $toolButton_horizontalLengths->set_tooltip_text('Horizontal exit length');
        $toolButton_horizontalLengths->signal_connect('clicked' => sub {

            $self->setExitLengthCallback('horizontal');
        });
        push (@buttonList, $toolButton_horizontalLengths);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'icon_horizontal_lengths', $toolButton_horizontalLengths);

        # Toolbutton for 'vertical exit length'
        my $toolButton_verticalLengths = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR
                . '/icons/map/icon_vertical_lengths.png'),
            'Vertical exit length',
        );
        $toolButton_verticalLengths->set_tooltip_text('Vertical exit length');
        $toolButton_verticalLengths->signal_connect('clicked' => sub {

            $self->setExitLengthCallback('vertical');
        });
        push (@buttonList, $toolButton_verticalLengths);
        # (Requires $self->currentRegionmap)
        $self->ivAdd('menuToolItemHash', 'icon_vertical_lengths', $toolButton_verticalLengths);

        # Separator
        my $separator2 = Gtk3::SeparatorToolItem->new();
        push (@buttonList, $separator2);

        # Toggle button for 'draw exit ornaments'
        my $toggleButton_drawExitOrnaments = Gtk3::ToggleToolButton->new();
        $toggleButton_drawExitOrnaments->set_active($self->worldModelObj->drawOrnamentsFlag);
        $toggleButton_drawExitOrnaments->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_draw_ornaments.png'),
        );
        $toggleButton_drawExitOrnaments->set_label('Draw exit ornaments');
        $toggleButton_drawExitOrnaments->set_tooltip_text('Draw exit ornaments');
        $toggleButton_drawExitOrnaments->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag) {

                $self->worldModelObj->toggleFlag(
                    'drawOrnamentsFlag',
                    $toggleButton_drawExitOrnaments->get_active(),
                    TRUE,      # Do call $self->redrawRegions
                    'draw_ornaments',
                    'icon_draw_ornaments',
                );
            }
        });
        push (@buttonList, $toggleButton_drawExitOrnaments);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_draw_ornaments', $toggleButton_drawExitOrnaments);

        # Toolbutton for 'no ornament'
        my $toolButton_noOrnament = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_no_ornament.png'),
            'Set no ornament',
        );
        $toolButton_noOrnament->set_tooltip_text('Set no ornament');
        $toolButton_noOrnament->signal_connect('clicked' => sub {

            $self->exitOrnamentCallback('none');
        });
        push (@buttonList, $toolButton_noOrnament);
        # (Requires $self->currentRegionmap & either $self->selectedExit or
        #   $self->selectedExitHash)
        $self->ivAdd('menuToolItemHash', 'icon_no_ornament', $toolButton_noOrnament);

        # Separator
        my $separator3 = Gtk3::SeparatorToolItem->new();
        push (@buttonList, $separator3);

        # Toolbutton for 'openable exit'
        my $toolButton_openableExit = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_openable_exit.png'),
            'Set openable exit',
        );
        $toolButton_openableExit->set_tooltip_text('Set openable exit');
        $toolButton_openableExit->signal_connect('clicked' => sub {

            $self->exitOrnamentCallback('open');
        });
        push (@buttonList, $toolButton_openableExit);
        # (Requires $self->currentRegionmap & either $self->selectedExit or
        #   $self->selectedExitHash)
        $self->ivAdd('menuToolItemHash', 'icon_openable_exit', $toolButton_openableExit);

        # Toolbutton for 'lockable exit'
        my $toolButton_lockableExit = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_lockable_exit.png'),
            'Set lockable exit',
        );
        $toolButton_lockableExit->set_tooltip_text('Set lockable exit');
        $toolButton_lockableExit->signal_connect('clicked' => sub {

            $self->exitOrnamentCallback('lock');
        });
        push (@buttonList, $toolButton_lockableExit);
        # (Requires $self->currentRegionmap & either $self->selectedExit or

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

            $self->centreMapOverRoom($self->selectedRoom);
        });
        push (@buttonList, $toolButton_centreSelectedRoom);
        # (Requires $self->currentRegionmap & $self->selectedRoom)
        $self->ivAdd(
            'menuToolItemHash',
            'icon_centre_map_selected_room',
            $toolButton_centreSelectedRoom,
        );

        # Toolbutton for 'centre map on last known room'
        my $toolButton_centreLastKnownRoom = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_centre_last.png'),
            'Centre map on last known room',
        );
        $toolButton_centreLastKnownRoom->set_tooltip_text('Centre map on last known room');
        $toolButton_centreLastKnownRoom->signal_connect('clicked' => sub {

            $self->centreMapOverRoom($self->mapObj->lastKnownRoom);
        });
        push (@buttonList, $toolButton_centreLastKnownRoom);
        # (Requires $self->currentRegionmap & $self->mapObj->lastknownRoom)
        $self->ivAdd(
            'menuToolItemHash',
            'icon_centre_map_last_known_room',
            $toolButton_centreLastKnownRoom,
        );

        # Toolbutton for 'centre map on middle of grid'
        my $toolButton_centreMiddleGrid = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_centre_middle.png'),
            'Centre map on middle of grid',
        );
        $toolButton_centreMiddleGrid->set_tooltip_text('Centre map on middle of grid');
        $toolButton_centreMiddleGrid->signal_connect('clicked' => sub {

            $self->setMapPosn(0.5, 0.5);
        });
        push (@buttonList, $toolButton_centreMiddleGrid);
        # (Requires $self->currentRegionmap)
        $self->ivAdd(
            'menuToolItemHash',
            'icon_centre_map_middle_grid',
            $toolButton_centreMiddleGrid,
        );

        # Separator
        my $separator = Gtk3::SeparatorToolItem->new();
        push (@buttonList, $separator);

        # Toggle button for 'track current room'
        my $toggleButton_trackCurrentRoom = Gtk3::ToggleToolButton->new();
        $toggleButton_trackCurrentRoom->set_active($self->worldModelObj->trackPosnFlag);
        $toggleButton_trackCurrentRoom->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_track_room.png'),
        );
        $toggleButton_trackCurrentRoom->set_label('Track current room');
        $toggleButton_trackCurrentRoom->set_tooltip_text('Track current room');
        $toggleButton_trackCurrentRoom->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag) {

                $self->worldModelObj->toggleFlag(
                    'trackPosnFlag',
                    $toggleButton_trackCurrentRoom->get_active(),
                    FALSE,      # Don't call $self->redrawRegions
                    'track_current_room',
                    'icon_track_current_room',
                );
            }
        });
        push (@buttonList, $toggleButton_trackCurrentRoom);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_track_current_room', $toggleButton_trackCurrentRoom);

        # Radio button for 'always track position'
        my $radioButton_trackAlways = Gtk3::RadioToolButton->new(undef);
        if (
            $self->worldModelObj->trackingSensitivity != 0.33
            && $self->worldModelObj->trackingSensitivity != 0.66
            && $self->worldModelObj->trackingSensitivity != 1
        ) {
            # Only the sensitivity values 0, 0.33, 0.66 and 1 are curently allowed; act as
            #   though the IV was set to 0
            $radioButton_trackAlways->set_active(TRUE);
        }
        $radioButton_trackAlways->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_track_always.png'),
        );
        $radioButton_trackAlways->set_label('Always track position');
        $radioButton_trackAlways->set_tooltip_text('Always track position');
        $radioButton_trackAlways->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_trackAlways->get_active()) {

                $self->worldModelObj->setTrackingSensitivity(0);
            }
        });
        push (@buttonList, $radioButton_trackAlways);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_track_always', $radioButton_trackAlways);

        # Radio button for 'track position near centre'
        my $radioButton_trackNearCentre = Gtk3::RadioToolButton->new_from_widget(
            $radioButton_trackAlways,
        );
        if ($self->worldModelObj->trackingSensitivity == 0.33) {

            $radioButton_trackNearCentre->set_active(TRUE);
        }
        $radioButton_trackNearCentre->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_track_centre.png'),
        );
        $radioButton_trackNearCentre->set_label('Track position near centre');
        $radioButton_trackNearCentre->set_tooltip_text('Track position near centre');
        $radioButton_trackNearCentre->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_trackNearCentre->get_active()) {

                $self->worldModelObj->setTrackingSensitivity(0.33);
            }
        });
        push (@buttonList, $radioButton_trackNearCentre);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_track_near_centre', $radioButton_trackNearCentre);

        # Radio button for 'track near edge'
        my $radioButton_trackNearEdge = Gtk3::RadioToolButton->new_from_widget(
            $radioButton_trackNearCentre,
        );
        if ($self->worldModelObj->trackingSensitivity == 0.66) {

            $radioButton_trackNearEdge->set_active(TRUE);
        }
        $radioButton_trackNearEdge->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_track_edge.png'),
        );
        $radioButton_trackNearEdge->set_label('Track position near edge');
        $radioButton_trackNearEdge->set_tooltip_text('Track position near edge');
        $radioButton_trackNearEdge->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_trackNearEdge->get_active()) {

                $self->worldModelObj->setTrackingSensitivity(0.66);
            }
        });
        push (@buttonList, $radioButton_trackNearEdge);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_track_near_edge', $radioButton_trackNearEdge);

        # Radio button for 'track if not visible'
        my $radioButton_trackNotVisible = Gtk3::RadioToolButton->new_from_widget(
            $radioButton_trackNearEdge,
        );
        if ($self->worldModelObj->trackingSensitivity == 1) {

            $radioButton_trackNotVisible->set_active(TRUE);
        }
        $radioButton_trackNotVisible->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_track_visible.png'),
        );
        $radioButton_trackNotVisible->set_label('Track if not visible');
        $radioButton_trackNotVisible->set_tooltip_text('Track position if not visible');
        $radioButton_trackNotVisible->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag && $radioButton_trackNotVisible->get_active()) {

                $self->worldModelObj->setTrackingSensitivity(1);
            }
        });
        push (@buttonList, $radioButton_trackNotVisible);
        # (Never desensitised)
        $self->ivAdd('menuToolItemHash', 'icon_track_not_visible', $radioButton_trackNotVisible);

        return @buttonList;
    }

    sub drawMiscButtonSet {

        # Called by $self->chooseButtonSet, which in turn was called by $self->drawToolbar or
        #   ->switchToolbarButtons
        # Draws buttons for this button set, and adds them to the toolbar
        #
        # Expected arguments
        #   $toolbar    - The toolbar widget on which the buttons are drawn
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $toolbar, $check) = @_;

        # Local variables
        my @buttonList;

        # Check for improper arguments
        if (! defined $toolbar || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->drawMiscButtonSet', @_);
        }

        # Toolbutton for 'increase visits and set current'
        my $toolButton_incVisitsCurrent = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file(
                $axmud::SHARE_DIR . '/icons/map/icon_inc_visits_current.png',
            ),
            'Increase visits and set current',
        );
        $toolButton_incVisitsCurrent->set_tooltip_text('Increase visits and set current');
        $toolButton_incVisitsCurrent->signal_connect('clicked' => sub {

            $self->updateVisitsCallback('increase');
            $self->mapObj->setCurrentRoom($self->selectedRoom);
        });
        push (@buttonList, $toolButton_incVisitsCurrent);
        # (Requires $self->currentRegionmap & $self->selectedRoom)
        $self->ivAdd('menuToolItemHash', 'icon_inc_visits_current', $toolButton_incVisitsCurrent);

        # Toolbutton for 'increase visits by one'
        my $toolButton_incVisits = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_inc_visits.png'),
            'Increase visits by one',
        );
        $toolButton_incVisits->set_tooltip_text('Increase visits by one');
        $toolButton_incVisits->signal_connect('clicked' => sub {

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        });
        push (@buttonList, $toolButton_addQuickWords);

        # Toolbutton for 'edit dictionary'
        my $toolButton_editDictionary = Gtk3::ToolButton->new(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_edit_dict.png'),
            'Edit current dictionary',
        );
        $toolButton_editDictionary->set_tooltip_text('Edit current dictionary');
        $toolButton_editDictionary->signal_connect('clicked' => sub {

            # Open an 'edit' window for the current dictionary
            $self->createFreeWin(
                'Games::Axmud::EditWin::Dict',
                $self,
                $self->session,
                'Edit \'' . $self->session->currentDict->name . '\' dictionary',
                $self->session->currentDict,
                FALSE,          # Not temporary
            );
        });
        push (@buttonList, $toolButton_editDictionary);

        return @buttonList;
    }

    sub drawFlagsButtonSet {

        # Called by $self->chooseButtonSet, which in turn was called by $self->drawToolbar or
        #   ->switchToolbarButtons
        # Draws buttons for this button set, and adds them to the toolbar
        #
        # Expected arguments
        #   $toolbar    - The toolbar widget on which the buttons are drawn
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $toolbar, $check) = @_;

        # Local variables
        my @buttonList;

        # Check for improper arguments
        if (! defined $toolbar || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->drawFlagsButtonSet', @_);
        }

        # Toggle button for 'release all filters'
        my $radioButton_releaseAllFilters = Gtk3::ToggleToolButton->new();
        $radioButton_releaseAllFilters->set_active($self->worldModelObj->allRoomFiltersFlag);
        $radioButton_releaseAllFilters->set_icon_widget(
            Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/icon_all_filters.png'),
        );
        $radioButton_releaseAllFilters->set_label('Release all filters');
        $radioButton_releaseAllFilters->set_tooltip_text('Release all filters');
        $radioButton_releaseAllFilters->signal_connect('toggled' => sub {

            if (! $self->ignoreMenuUpdateFlag) {

                $self->worldModelObj->toggleFlag(
                    'allRoomFiltersFlag',
                    $radioButton_releaseAllFilters->get_active(),
                    TRUE,      # Do call $self->redrawRegions
                    'release_all_filters',
                    'icon_release_all_filters',
                );
            }
        });
        push (@buttonList, $radioButton_releaseAllFilters);
        # (Never desensitised)
        $self->ivAdd(
            'menuToolItemHash',
            'icon_release_all_filters',
            $radioButton_releaseAllFilters,
        );

        # Separator
        my $separator = Gtk3::SeparatorToolItem->new();
        push (@buttonList, $separator);

        # Filter icons
        foreach my $filter ($axmud::CLIENT->constRoomFilterList) {

            # Filter button
            my $toolButton_filter = Gtk3::ToggleToolButton->new();
            $toolButton_filter->set_active(
                $self->worldModelObj->ivShow('roomFilterApplyHash', $filter),
            );
            $toolButton_filter->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag) {

                    $self->worldModelObj->toggleFilter(
                        $filter,
                        $toolButton_filter->get_active(),
                    );
                }
            });

            # If it's one of the standard filters, we can use one of the existing icons;
            #   otherwise, use a spare icon
            my $iconFile = $axmud::SHARE_DIR . '/icons/map/icon_' . $filter . '.png';
            if (! -e $iconFile) {

                $iconFile = $axmud::SHARE_DIR . '/icons/map/icon_spare_filter.png'
            }

            $toolButton_filter->set_icon_widget(
                Gtk3::Image->new_from_file($iconFile)
            );

            $toolButton_filter->set_label('Toggle ' . $filter . ' filter');
            $toolButton_filter->set_tooltip_text('Toggle ' . $filter . ' filter');
            push (@buttonList, $toolButton_filter);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'icon_' . $filter . '_filter', $toolButton_filter);
        }

        return @buttonList;
    }

    sub drawInteriorsButtonSet {

        # Called by $self->chooseButtonSet, which in turn was called by $self->drawToolbar or
        #   ->switchToolbarButtons
        # Draws buttons for this button set, and adds them to the toolbar
        #
        # Expected arguments
        #   $toolbar    - The toolbar widget on which the buttons are drawn
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $toolbar, $check) = @_;

        # Local variables
        my (
            $lastButton,
            @initList, @interiorList, @buttonList,
            %interiorHash, %iconHash,
        );

        # Check for improper arguments
        if (! defined $toolbar || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->drawInteriorsButtonSet', @_);
        }

        @initList = (
            'none',

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

            'profile_count',
                'Draw exclusive profiles',
                'icon_draw_exclusive.png',
            'title_descrip',
                'Draw titles/descriptions',
                'icon_draw_descrips.png',
            'exit_pattern',
                'Draw exit patterns',
                'icon_draw_patterns.png',
            'source_code',
                'Draw room source code',
                'icon_draw_code.png',
            'vnum',
                'Draw world\'s room _vnum',
                'icon_draw_vnum.png',
            'grid_posn',
                'Draw grid position',
                'icon_draw_grid_posn.png',
        );

        do {

            my ($mode, $descrip, $icon);

            $mode = shift @initList;
            $descrip = shift @initList;
            $icon = shift @initList;

            push (@interiorList, $mode);
            $interiorHash{$mode} = $descrip;
            $iconHash{$mode} = $icon;

        } until (! @initList);

        for (my $count = 0; $count < (scalar @interiorList); $count++) {

            my ($icon, $mode);

            $mode = $interiorList[$count];

            # (For $count = 0, $buttonGroup is 'undef')
            my $radioButton;
            if ($mode eq 'none') {
                $radioButton = Gtk3::RadioToolButton->new(undef);
            } else {
                $radioButton = Gtk3::RadioToolButton->new_from_widget($lastButton);
            }

            if ($self->worldModelObj->roomInteriorMode eq $mode) {

                $radioButton->set_active(TRUE);
            }
            $radioButton->set_icon_widget(
                Gtk3::Image->new_from_file($axmud::SHARE_DIR . '/icons/map/' . $iconHash{$mode}),
            );
            $radioButton->set_label($interiorHash{$mode});
            $radioButton->set_tooltip_text($interiorHash{$mode});

            $radioButton->signal_connect('toggled' => sub {

                if (! $self->ignoreMenuUpdateFlag && $radioButton->get_active()) {

                    $self->worldModelObj->switchRoomInteriorMode($mode);
                }
            });
            push (@buttonList, $radioButton);
            # (Never desensitised)
            $self->ivAdd('menuToolItemHash', 'icon_interior_mode_' . $mode, $radioButton);

            $lastButton = $radioButton;

            # (Add a separator after the first toolbar button)
            if ($mode eq 'none') {

                # Separator
                my $separator = Gtk3::SeparatorToolItem->new();
                push (@buttonList, $separator);
            }
        }

        return @buttonList;
    }

    sub addRoomFlagButton {

        # Called by a ->signal_connect in $self->drawPaintingButtonSet whenever the user clicks the
        #   'add room flag' button in the 'painting' button set
        # Creates a popup menu containing all room flags, then implements the user's choice
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if there's an error
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my %checkHash;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->addRoomFlagButton', @_);
        }

        # Compile a hash of existing preferred room flags (we don't want the user to add the same
        #   room flag twice)
        foreach my $roomFlag ($self->worldModelObj->preferRoomFlagList) {

            $checkHash{$roomFlag} = undef;
        }

        # Set up the popup menu
        my $popupMenu = Gtk3::Menu->new();
        if (! $popupMenu) {

            return undef;
        }

        # Add a title menu item, which does nothing
        my $title_item = Gtk3::MenuItem->new('Add preferred room flag:');
        $title_item->signal_connect('activate' => sub {

            return undef;
        });
        $title_item->set_sensitive(FALSE);
        $popupMenu->append($title_item);

        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        # Fill the popup menu with room flags
        foreach my $filter ($axmud::CLIENT->constRoomFilterList) {

            # A sub-sub menu for $filter
            my $subSubMenu_filter = Gtk3::Menu->new();

            my @nameList = $self->worldModelObj->getRoomFlagsInFilter($filter);
            foreach my $name (@nameList) {

                my $obj = $self->worldModelObj->ivShow('roomFlagHash', $name);
                if ($obj) {

                    my $menuItem = Gtk3::MenuItem->new($obj->descrip);
                    $menuItem->signal_connect('activate' => sub {

                        # Add the room flag to the world model's list of preferred room flags...
                        $self->worldModelObj->add_preferRoomFlag($name);
                        # ...then redraw the window component containing the toolbar(s), toggling
                        #   the button for the new room flag
                        $self->redrawWidgets('toolbar');
                    });
                    $subSubMenu_filter->append($menuItem);
                }
            }

            if (! @nameList) {

                my $menuItem = Gtk3::MenuItem->new('(No flags in this filter)');
                $menuItem->set_sensitive(FALSE);
                $subSubMenu_filter->append($menuItem);
            }

            my $menuItem = Gtk3::MenuItem->new(ucfirst($filter));
            $menuItem->set_submenu($subSubMenu_filter);
            $popupMenu->append($menuItem);
        }

        # Also add a 'Cancel' menu item, which does nothing
        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        my $cancel_item = Gtk3::MenuItem->new('Cancel');
        $cancel_item->signal_connect('activate' => sub {

            return undef;
        });
        $popupMenu->append($cancel_item);

        # Display the popup menu
        $popupMenu->popup(
            undef, undef, undef, undef,
            1,                              # Left mouse button
            Gtk3::get_current_event_time(),
        );

        $popupMenu->show_all();

        # Operation complete. Now wait for the user's response
        return 1;
    }

    sub removeRoomFlagButton {

        # Called by a ->signal_connect in $self->drawPaintingButtonSet whenever the user clicks the
        #   'remove room flag' button in the 'painting' button set
        # Removes the specified room flag from the toolbar and updates IVs
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if there's an error
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->removeRoomFlagButton', @_);
        }

        # Set up the popup menu
        my $popupMenu = Gtk3::Menu->new();
        if (! $popupMenu) {

            return undef;
        }

        # Add a title menu item, which does nothing
        my $title_item = Gtk3::MenuItem->new('Remove preferred room flag:');
        $title_item->signal_connect('activate' => sub {

            return undef;
        });
        $title_item->set_sensitive(FALSE);
        $popupMenu->append($title_item);

        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        # Fill the popup menu with room flags
        foreach my $roomFlag ($self->worldModelObj->preferRoomFlagList) {

            my $menu_item = Gtk3::MenuItem->new($roomFlag);
            $menu_item->signal_connect('activate' => sub {

                # Remove the room flag from the world model's list of preferred room flags...
                $self->worldModelObj->del_preferRoomFlag($roomFlag);
                # ...and from the painter object iself...
                $self->worldModelObj->painterObj->ivDelete('roomFlagHash', $roomFlag);
                # ...then redraw the window component containing the toolbar(s)
                $self->redrawWidgets('toolbar');
            });
            $popupMenu->append($menu_item);
        }

        # Add a 'remove all' menu item
        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        my $remove_all_item = Gtk3::MenuItem->new('Remove all');
        $remove_all_item->signal_connect('activate' => sub {

            my ($total, $choice);

            $total = scalar $self->worldModelObj->preferRoomFlagList;

            # If there's more than one colour, prompt the user for confirmation
            if ($total > 1) {

                $choice = $self->showMsgDialogue(
                    'Remove all room flag buttons',
                    'question',
                    'Are you sure you want to remove all ' . $total . ' room flag buttons?',
                    'yes-no',
                );

            } else {

                $choice = 'yes';
            }

            if (defined $choice && $choice eq 'yes') {

                # Reset the world model's list of preferred room flags
                $self->worldModelObj->reset_preferRoomFlagList();

                # Update the painter object (which might contain room flags not added with these
                #   tools)
                foreach my $roomFlag ($self->worldModelObj->preferRoomFlagList) {

                    $self->worldModelObj->ivDelete('roomFlagHash', $roomFlag);
                }

                # Then redraw the window component containing the toolbar(s)
                $self->redrawWidgets('toolbar');
            }
        });
        $popupMenu->append($remove_all_item);

        # Also add a 'Cancel' menu item, which does nothing
        my $cancel_item = Gtk3::MenuItem->new('Cancel');
        $cancel_item->signal_connect('activate' => sub {

            return undef;
        });
        $popupMenu->append($cancel_item);

        # Display the popup menu
        $popupMenu->popup(
            undef, undef, undef, undef,
            1,                              # Left mouse button
            Gtk3::get_current_event_time(),
        );

        $popupMenu->show_all();

        # Operation complete. Now wait for the user's response
        return 1;
    }

    sub addBGColourButton {

        # Called by a ->signal_connect in $self->drawBackgroundButtonSet whenever the user clicks
        #   the 'add background colour' button in the 'background' button set
        # Prompts the user for a new RGB colour tag, then implements the user's choice
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if there's an error
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $colour;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->addBGColourButton', @_);
        }

        $colour = $self->showColourSelectionDialogue('Add preferred background colour');
        if (defined $colour) {

            # Add the room flag to the world model's list of preferred background colours...
            $self->worldModelObj->add_preferBGColour($colour);
            # ...then redraw the window component containing the toolbar(s), selecting the new
            #   colour
            $self->ivPoke('bgColourChoice', $colour);
            $self->redrawWidgets('toolbar');
        }

        return 1;
    }

    sub removeBGColourButton {

        # Called by a ->signal_connect in $self->drawBackgroundButtonSet whenever the user clicks
        #   the 'remove background colour' button in the 'background' button set
        # Removes the specified colour from the toolbar and updates IVs
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if there's an error
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->removeBGColourButton', @_);
        }

        # Set up the popup menu
        my $popupMenu = Gtk3::Menu->new();
        if (! $popupMenu) {

            return undef;
        }

        # Add a title menu item, which does nothing
        my $title_item = Gtk3::MenuItem->new('Remove preferred background colour:');
        $title_item->signal_connect('activate' => sub {

            return undef;
        });
        $title_item->set_sensitive(FALSE);
        $popupMenu->append($title_item);

        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        # Fill the popup menu with colours
        foreach my $colour ($self->worldModelObj->preferBGColourList) {

            my $menu_item = Gtk3::MenuItem->new($colour);
            $menu_item->signal_connect('activate' => sub {

                # Remove the colour from the world model's list of preferred background colours...
                $self->worldModelObj->del_preferBGColour($colour);
                # ...then redraw the window component containing the toolbar(s)
                $self->redrawWidgets('toolbar');
            });
            $popupMenu->append($menu_item);
        }

        # Add a 'remove all' menu item
        $popupMenu->append(Gtk3::SeparatorMenuItem->new());     # Separator

        my $remove_all_item = Gtk3::MenuItem->new('Remove all');
        $remove_all_item->signal_connect('activate' => sub {

            my ($total, $choice);

            $total = scalar $self->worldModelObj->preferBGColourList;

            # If there's more than one colour, prompt the user for confirmation
            if ($total > 1) {

                $choice = $self->showMsgDialogue(
                    'Remove all colour buttons',
                    'question',
                    'Are you sure you want to remove all ' . $total . ' colour buttons?',
                    'yes-no',
                );

            } else {

                $choice = 'yes';
            }

            if (defined $choice && $choice eq 'yes') {

                # Reset the world model's list of preferred background colour...
                $self->worldModelObj->reset_preferBGColourList();
                # ...then redraw the window component containing the toolbar(s)
                $self->redrawWidgets('toolbar');
            }
        });
        $popupMenu->append($remove_all_item);

        # Also add a 'Cancel' menu item, which does nothing
        my $cancel_item = Gtk3::MenuItem->new('Cancel');
        $cancel_item->signal_connect('activate' => sub {

            return undef;
        });
        $popupMenu->append($cancel_item);

        # Display the popup menu
        $popupMenu->popup(
            undef, undef, undef, undef,
            1,                              # Left mouse button
            Gtk3::get_current_event_time(),
        );

        $popupMenu->show_all();

        # Operation complete. Now wait for the user's response
        return 1;
    }

    # Treeview widget methods

    sub enableTreeView {

        # Called by $self->drawWidgets
        # Sets up the Automapper window's treeview widget
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the widget can't be created
        #   Otherwise returns the Gtk3::ScrolledWindow containing the Gtk3::TreeView created

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->enableTreeView', @_);
        }

        # Create the treeview
        my $objectModel = Gtk3::TreeStore->new( ['Glib::String'] );
        my $treeView = Gtk3::TreeView->new($objectModel);
        if (! $objectModel || ! $treeView) {

            return undef;
        }

        # No interactive searches required
        $treeView->set_enable_search(FALSE);

        # Append a single column to the treeview
        $treeView->append_column(
            Gtk3::TreeViewColumn->new_with_attributes(
                'Regions',
                Gtk3::CellRendererText->new,
                markup => 0,
            )
        );

        # Make the treeview scrollable
        my $treeViewScroller = Gtk3::ScrolledWindow->new;
        $treeViewScroller->add($treeView);
        $treeViewScroller->set_policy(qw/automatic automatic/);

        # Make the branches of the list tree clickable, so the rows can be expanded and collapsed
        $treeView->signal_connect('row_activated' => sub {

            my ($treeView, $path, $column) = @_;

            $self->treeViewRowActivatedCallback();
        });

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        } elsif (! defined $xPosBlocks || ! defined $yPosBlocks) {

            # Can't do anything without arguments
            return undef;
        }

        # Convert that position into canvas coordinates, and centre the map at that position
        ($blockCentreXPosPixels, $blockCentreYPosPixels) = $self->getBlockCentre(
            $roomObj->xPosBlocks,
            $roomObj->yPosBlocks,
        );

        $self->setMapPosn(
            ($blockCentreXPosPixels / $self->currentRegionmap->mapWidthPixels),
            ($blockCentreYPosPixels / $self->currentRegionmap->mapHeightPixels),
        );

        return 1;
    }

    sub doZoom {

        # Called by $self->worldModelObj->setMagnification
        # Zooms the map in or out, depending on the new value of
        #   $self->currentRegionmap->magnification
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if no arguments are specified at all
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            @redrawList,
            %newHash,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->doZoom', @_);
        }

        # Set the visible map's size. Each GooCanvas2::Canvas automatically takes care of its
        #   position, so that the same part of the map is visible in the window
        foreach my $canvasWidget ($self->currentParchment->ivValues('canvasWidgetHash')) {

            $canvasWidget->set_scale($self->currentRegionmap->magnification);
        }

        # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
        $self->restrictWidgets();

        return 1;
    }

    # Menu bar/toolbar widget sensitisers

    sub restrictWidgets {

        # Many menu bar and toolbar items can be sensitised, or desensitised, depending on
        #   conditions
        # This function can be called by anything, any time one of those conditions changes, so that
        #   every menu bar/toolbar item can be sensitised or desensitised correctly
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $regionObj,
            @list, @sensitiseList, @desensitiseList, @magList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->restrictWidgets', @_);
        }

        # Modified v1.0.150 - anything that requires the current regionmap, also requires
        #   the character to be logged in (with a handful of exceptions)
        # Modified v1.0.363 - we now allow zooming and a few other things from the 'View' menu
        #   when the character isn't logged in

        # Menu items that require a current regionmap AND a logged in character
        @list = (
            'select', 'unselect_all',
            'selected_objs',
            'set_follow_mode', 'icon_set_follow_mode',
            'screenshots', 'icon_visible_screenshot',
            'drag_mode', 'icon_drag_mode',
            'graffiti_mode', 'icon_graffiti_mode',
            'edit_region',
            'edit_regionmap',
            'current_region',
            'redraw_region',
            'recalculate_paths',
            'exit_tags',
            'exit_options',
            'empty_region',
            'delete_region',
            'add_room',
            'add_label_at_click',
            'add_label_at_block',
            'room_text',
            'other_room_features',
            'select_label',
            'report_region',
            'report_visits_2',
            'report_guilds_2',
            'report_flags_2',
            'report_flags_4',
            'report_rooms_2',
            'report_exits_2',
            'report_checked_2',
            'reset_locator', 'icon_reset_locator',
        );

        if ($self->currentRegionmap && $self->session->loginFlag) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap BUT NOT a logged in character
        @list = (
            'zoom_sub',
            'level_sub',
            'centre_map_middle_grid', 'icon_centre_map_middle_grid',
            'centre_map_sub',
            'move_up_level', 'icon_move_up_level',
            'move_down_level', 'icon_move_down_level',
            'this_region_scheme',
            'exit_lengths', 'icon_horizontal_lengths', 'icon_vertical_lengths',
        );

        if ($self->currentRegionmap) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, GA::Obj::WorldModel->disableUpdateModeFlag
        #   set to FALSE and a session not in 'connect offline' mode
        @list = (
            'set_update_mode', 'icon_set_update_mode',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ! $self->worldModelObj->disableUpdateModeFlag
            && $self->session->status ne 'offline'
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap for a region that doesn't have a parent region
        @list = (
            'move_region_top',
        );

        if ($self->currentRegionmap) {

            $regionObj
                = $self->worldModelObj->ivShow('regionModelHash', $self->currentRegionmap->number);
        }

        if ($regionObj && ! $regionObj->parent) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a current room
        @list = (
            'centre_map_current_room', 'icon_centre_map_current_room',
            'add_room_contents',
            'add_hidden_object',
            'add_search_result',
            'unset_current_room',
            'update_locator',
            'repaint_current',
            'execute_scripts',
            'add_failed_room',
            'add_involuntary_exit',
            'add_repulse_exit',
            'add_special_depart',
            'add_unspecified_pattern',
            'icon_fail_exit',
        );

        if ($self->currentRegionmap && $self->session->loginFlag && $self->mapObj->currentRoom) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected room
        @list = (
            'centre_map_selected_room', 'icon_centre_map_selected_room',
            'set_current_room', 'icon_set_current_room',
            'select_exit',
            'increase_set_current',
            'edit_room',
            'set_file_path',
            'add_contents_string',
            'add_hidden_string',
            'add_exclusive_prof',
            'icon_inc_visits_current',
        );

        if ($self->currentRegionmap && $self->session->loginFlag && $self->selectedRoom) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and either a single selected room or a single
        #   selected room tag
        @list = (
            'set_room_tag',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedRoom || $self->selectedRoomTag)
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, a current room and a single selected room
        #   (the current room and selected room shouldn't be the same)
        @list = (
            'path_finding_highlight',
            'path_finding_edit',
            'path_finding_go',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->mapObj->currentRoom
            && $self->selectedRoom
            && $self->mapObj->currentRoom ne $self->selectedRoom
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and either a current room or a single selected
        #   room
        @list = (
            'add_to_model',
        );
        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->mapObj->currentRoom || $self->selectedRoom)
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected room with one or more
        #   checked directions
        @list = (
            'remove_checked', 'remove_checked_all',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedRoom
            && $self->selectedRoom->checkedDirHash
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected room with
        #   ->sourceCodePath set, but ->virtualAreaPath not set
        @list = (
            'view_source_code',
            'edit_source_code',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedRoom
            && $self->selectedRoom->sourceCodePath
            && ! $self->selectedRoom->virtualAreaPath
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected room with
        #   ->virtualAreaPath set
        @list = (
            'view_virtual_area',
            'edit_virtual_area',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedRoom
            && $self->selectedRoom->virtualAreaPath
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected room whose ->wildMode
        #   is not set to 'wild' (the value 'border' is ok, though)
        @list = (
            'add_normal_exit', 'add_hidden_exit',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedRoom
            && $self->selectedRoom->wildMode ne 'wild'
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, one or more selected rooms and
        #   $self->graffitiModeFlag set to TRUE
        @list = (
            'toggle_graffiti', 'icon_toggle_graffiti',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedRoom || $self->selectedRoomHash)
            && $self->graffitiModeFlag
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and one or more selected rooms
        @list = (
            'move_rooms_dir', 'move_rooms_click',
            'icon_move_to_click',
            'toggle_room_flag_sub',
            'reset_positions',
            'room_exclusivity', 'room_exclusivity_sub',
            'set_exits',
            'add_multiple_exits',
            'wilderness_normal',
            'update_visits',
            'delete_room',
            'repaint_selected',
            'set_virtual_area',
            'reset_virtual_area',
            'toggle_exclusivity',
            'clear_exclusive_profs',
            'connect_adjacent',
            'icon_inc_visits', 'icon_dec_visits', 'icon_set_visits', 'icon_reset_visits',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedRoom || $self->selectedRoomHash)
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and either one or more selected rooms or one
        #   or more selected room guilds (or a mixture of both)
        @list = (
            'set_room_guild',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && (
                $self->selectedRoom || $self->selectedRoomHash || $self->selectedRoomGuild
                || $self->selectedRoomGuildHash
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and EITHER one or more selected rooms OR a
        #   current room
        @list = (
            'identify_room',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedRoom || $self->selectedRoomHash || $self->mapObj->currentRoom)
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, one or more selected rooms and at least two
        #   regions in the world model
        @list = (
            'transfer_to_region',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedRoom || $self->selectedRoomHash)
            && $self->worldModelObj->ivPairs('regionmapHash') > 1
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, a current room and the automapper object
        #   being set up to perform a merge operation
        @list = (
            'move_merge_rooms',
        );

        if (
            $self->currentRegionmap
            && $self->mapObj->currentRoom
            && $self->mapObj->currentMatchFlag
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and EITHER one or more selected rooms OR a
        #   current room and the automapper being set up to perform a merge)
        @list = (
            'move_rooms_labels',
        );

        if (
            $self->currentRegionmap
            && (
                $self->selectedRoom
                || $self->selectedRoomHash
                || ($self->mapObj->currentRoom && $self->mapObj->currentMatchFlag)
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and an empty
        #   $self->currentRegionmap->gridRoomHash
        @list = (
            'add_first_room',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ! $self->currentRegionmap->gridRoomHash
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Meny items that require a current regionmap and a non-empty
        #   $self->currentRegionmap->gridRoomHash
        @list = (
            'recalculate_in_region',
            'locate_room_in_current',
        );
        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->currentRegionmap->gridRoomHash
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected exit
        @list = (
            'set_exit_dir',
            'edit_exit',
            'disconnect_exit',
            'delete_exit',
            'set_exit_type',
        );

        if ($self->currentRegionmap && $self->session->loginFlag && $self->selectedExit) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and one or more selected exits
        @list = (
            'set_ornament_sub',
            'icon_no_ornament', 'icon_openable_exit', 'icon_lockable_exit',
            'icon_pickable_exit', 'icon_breakable_exit', 'icon_impassable_exit',
            'icon_mystery_exit',
            'identify_exit',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedExit || $self->selectedExitHash)
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, a single selected exit and
        #   $self->selectedExit->drawMode is 'temp_alloc' or 'temp_unalloc'
        @list = (
            'allocate_map_dir',
            'allocate_shadow',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && (
                $self->selectedExit->drawMode eq 'temp_alloc'
                || $self->selectedExit->drawMode eq 'temp_unalloc'
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, a single selected exit and
        #   $self->selectedExit->drawMode is 'primary' or 'perm_alloc'
        @list = (
            'change_direction',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && (
                $self->selectedExit->drawMode eq 'primary'
                || $self->selectedExit->drawMode eq 'perm_alloc'
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, a single selected exit and
        #   $self->selectedExit->drawMode is 'primary', 'temp_unalloc' or 'perm_alloc'
        @list = (
            'connect_to_click',
            'set_assisted_move',
            'icon_connect_click',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && $self->selectedExit->drawMode ne 'temp_alloc'
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected exit which is a broken
        #   exit
        @list = (
            'toggle_bent_exit',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && $self->selectedExit->brokenFlag
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected exit which is a region
        #   exit
        @list = (
            'set_super_sub',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && $self->selectedExit->regionFlag
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and either a single selected exit which is a
        #   region exit, or a single selected exit tag
        @list = (
            'toggle_exit_tag',
            'edit_tag_text',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && (
                ($self->selectedExit && $self->selectedExit->regionFlag)
                || $self->selectedExitTag
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and one or more selected exits or selected
        #   exit tags
        @list = (
            'reset_exit_tags',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && (
                $self->selectedExit || $self->selectedExitHash
                || $self->selectedExitTag || $self->selectedExitTagHash
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected exit which is a
        #   super-region exit
        @list = (
            'recalculate_from_exit',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && $self->selectedExit->superFlag
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected exit which is an
        #   uncertain exit or a one-way exit
        @list = (
            'set_exit_twin',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && (
                $self->selectedExit->oneWayFlag
                || (
                    $self->selectedExit->destRoom
                    && ! $self->selectedExit->twinExit
                    && ! $self->selectedExit->retraceFlag
                    && $self->selectedExit->randomType eq 'none'
                )
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected exit which is a one-way
        #   exit
        @list = (
            'set_incoming_dir',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->selectedExit
            && $self->selectedExit->oneWayFlag
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a single selected label
        @list = (
            'set_label',
            'customise_label',
        );

        if ($self->currentRegionmap && $self->session->loginFlag && $self->selectedLabel) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and one or more selected labels
        @list = (
            'delete_label',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedLabel || $self->selectedLabelHash)
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a selected region (in the treeview)
        @list = (
            'identify_region',
        );

        if ($self->treeViewSelectedLine) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, and $self->currentRegionmap->magnification
        #   to be within a certain range of values
        @magList = $self->constMagnifyList;

        @list = (
            'zoom_out',
        );

        # (Don't try to zoom out, if already zoomed out to the maximum extent)
        if (
            $self->currentRegionmap
            && $self->currentRegionmap->magnification > $magList[0]
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        @list = (
            'zoom_in',
        );

        # (Don't try to zoom in, if already zoomed in to the maximum extent)
        if (
            $self->currentRegionmap
            && $self->currentRegionmap->magnification < $magList[-1]
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and $self->worldModelObj->drawExitMode is
        #   'ask_regionmap'
        @list = (
            'draw_region_exits',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && $self->worldModelObj->drawExitMode eq 'ask_regionmap'
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current character profile
        @list = (
            'report_visits_3',
        );

        if ($self->session->currentChar) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a current character profile
        @list = (
            'report_visits_4',
        );

        if ($self->currentRegionmap && $self->session->loginFlag && $self->session->currentChar) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current guild profile
        @list = (
            'report_guilds_3',
        );

        if ($self->session->currentGuild) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap whose ->gridColourBlockHash and/or
        #   ->gridColourObjHash is not empty)
        @list = (
            'empty_bg_colours',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && (
                $self->currentRegionmap->gridColourBlockHash
                || $self->currentRegionmap->gridColourObjHash
            )
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and a current guild profile
        @list = (
            'report_guilds_4',
        );

        if ($self->currentRegionmap && $self->session->loginFlag && $self->session->currentGuild) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require assisted moves to be turned on
        @list = (
            'allow_protected_moves',
            'allow_super_protected_moves',
        );

        if ($self->worldModelObj->assistedMovesFlag) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require protected moves to be turned off
        @list = (
            'allow_crafty_moves',
        );

        if (! $self->worldModelObj->protectedMovesFlag) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require basic mapping mode to be turned off
        @list = (
            'paint_wild', 'icon_paint_wild',
            'paint_border', 'icon_paint_border',
        );

        if (! $self->session->currentWorld->basicMappingFlag) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap, one or more selected rooms and basic mapping
        #   mode to be turned off
        @list = (
            'wilderness_wild', 'wilderness_border',
        );

        if (
            $self->currentRegionmap
            && $self->session->loginFlag
            && ($self->selectedRoom || $self->selectedRoomHash)
            && ! $self->session->currentWorld->basicMappingFlag
        ) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a non-empty list of preferred room flags
        @list = (
            'icon_remove_room_flag', 'icon_remove_room_flag_2',
        );

        if ($self->worldModelObj->preferRoomFlagList) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a non-empty list of preferred background colours
        @list = (
            'icon_bg_remove',
        );

        if ($self->worldModelObj->preferBGColourList) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap and at least one non-default colour scheme
        @list = (
            'attach_region_scheme',
        );

        if ($self->currentRegionmap && $self->worldModelObj->ivPairs('regionSchemeHash') > 1) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require a current regionmap with a non-default region scheme attached
        @list = (
            'detach_region_scheme',
        );

        if ($self->currentRegionmap && defined $self->currentRegionmap->regionScheme) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Menu items that require at least one map label style
        @list = (
            'edit_style',
        );

        if ($self->worldModelObj->mapLabelStyleHash) {
            push (@sensitiseList, @list);
        } else {
            push (@desensitiseList, @list);
        }

        # Sensitise and desensitise menu items and toolbar buttons, as required
        $self->sensitiseWidgets(@sensitiseList);
        $self->desensitiseWidgets(@desensitiseList);

        return 1;
    }

    sub sensitiseWidgets {

        # Called by anything. Frequently called by $self->restrictWidgets
        # Given a list of Gtk3 widgets (all of them menu/toolbar items), sets them as sensitive
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   @widgetList - A list of widgets - keys in the hash $self->menuToolItemHash
        #                 (e.g. 'move_up_level')
        #
        # Return values
        #   1

        my ($self, @widgetList) = @_;

        # (No improper arguments to check)

        foreach my $widgetName (@widgetList) {

            my $widget = $self->ivShow('menuToolItemHash', $widgetName);
            if ($widget) {

                $widget->set_sensitive(TRUE);
            }
        }

        return 1;
    }

    sub desensitiseWidgets {

        # Called by anything. Frequently called by $self->restrictWidgets
        # Given a list of Gtk3 widgets (all of them menu/toolbar items), sets them as insensitive
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   @widgetList - A list of widgets - keys in the hash $self->menuToolItemHash
        #                 (e.g. 'move_up_level')
        #

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        # Can be called by anything, but mostly called by functions in GA::Obj::WorldModel that want
        #   to set a menu bar or toolbar item as active (or not)
        #
        # Expected arguments
        #   $widgetName - The widget's name, a key in the hash $self->menuToolItemHash
        #
        # Optional arguments
        #   $flag       - Any TRUE value to set the menu bar/toolbar item as active, any FALSE value
        #                   (including 'undef') to set the item as not active
        #
        # Return values
        #   'undef' on improper arguments or if $widgetName doesn't appear in
        #       $self->menuToolItemHash
        #   1 otherwise

        my ($self, $widgetName, $flag, $check) = @_;

        # Local variables
        my $widget;

        # Check for improper arguments
        if (! defined $widgetName || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->setActiveItem', @_);
        }

        $widget = $self->ivShow('menuToolItemHash', $widgetName);
        if (! defined $widget) {

            return undef;

        } else {

            if (! $flag) {
                $widget->set_active(FALSE);
            } else {
                $widget->set_active(TRUE);
            }

            return 1;
        }
    }

    sub restrictUpdateMode {

        # Called by $self->setMode
        # Sensitises or desensitises the menu and toolbar buttons that allow the user to switch to
        #   update mode, depending on the value of GA::Obj::WorldModel->disableUpdateModeFlag and
        #   GA::Session->status
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($radioMenuItem, $toolbarButton);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->restrictUpdateMode', @_);
        }

        # Mark the radio/toolbar buttons for 'update mode' as sensitive, or not
        $radioMenuItem = $self->ivShow('menuToolItemHash', 'set_update_mode');
        $toolbarButton = $self->ivShow('menuToolItemHash', 'icon_set_update_mode');

        if ($self->worldModelObj->disableUpdateModeFlag || $self->session->status eq 'offline') {

            if ($radioMenuItem) {

                $radioMenuItem->set_sensitive(FALSE);
            }

            if ($toolbarButton) {

                $toolbarButton->set_sensitive(FALSE);
            }

        } else {

            if ($radioMenuItem) {

                $radioMenuItem->set_sensitive(TRUE);
            }

            if ($toolbarButton) {

                $toolbarButton->set_sensitive(TRUE);
            }
        }

        return 1;
    }

    # Pause windows handlers

    sub showPauseWin {

        # Can be called by anything
        # Makes the pause window visible (a 'dialogue' window used only by this automapper)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->showPauseWin', @_);
        }

        if (! $axmud::CLIENT->busyWin) {

            # Show the window widget
            $self->showBusyWin(
                $axmud::SHARE_DIR . '/icons/system/mapper.png',
                'Working...',
            );
        }

        return 1;
    }

    sub hidePauseWin {

        # Can be called by anything
        # Makes the pause window invisible
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                # Continue the drag operation by re-drawing the object(s) at their new position
                $self->continueDrag($event, $event->x_root, $event->y_root);
            }
        });

        $canvasObj->signal_connect('enter_notify_event' => sub {

            my ($item, $target, $event) = @_;

            if ($modelObj && $self->worldModelObj->showTooltipsFlag && ! $self->canvasTooltipObj) {

                # Show the tooltips window
                $self->showTooltips($type, $canvasObj, $modelObj);
            }
        });

        $canvasObj->signal_connect('leave_notify_event' => sub {

            my ($item, $target, $event) = @_;

            if (
                $modelObj
                && $self->canvasTooltipFlag
                && $self->canvasTooltipObj eq $canvasObj
                && $self->canvasTooltipObjType eq $type
            ) {
                # Hide the tooltips window
                $self->hideTooltips();
            }
        });

        # Setup complete
        return 1;
    }

    sub canvasEventHandler {

        # Handles events on the map background (i.e. clicking on an empty part of the background
        #   which doesn't contain a room, room tag, room guild, exit, exit tag, label, or checked
        #   direction)
        # The calling function, an anonymous sub defined in $self->setupCanvasEvent, filters out the
        #   signals we don't want
        # At the moment, the signals let through the filter are:
        #   button_press, 2button_press, 3button_press, button_release
        #
        # Expected arguments
        #   $canvasObj  - The canvas object which intercepted an event signal
        #   $event      - The Gtk3::Gdk::Event that caused the signal
        #
        # Return values
        #   'undef' on improper arguments, if there is no region map or if the signal $event is one
        #       that this function doesn't handle
        #   1 otherwise

        my ($self, $canvasObj, $event, $check) = @_;

        # Local variables
        my (
            $clickXPosPixels, $clickYPosPixels, $clickType, $button, $shiftFlag, $ctrlFlag,
            $clickXPosBlocks, $clickYPosBlocks, $newRoomObj, $roomNum, $roomObj, $exitObj, $listRef,
            $result, $twinExitObj, $result2, $popupMenu,
        );

        # Check for improper arguments
        if (! defined $canvasObj || ! defined $event || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->canvasEventHandler', @_);
        }

        # Don't do anything if there is no current regionmap
        if (! $self->currentRegionmap) {

            return undef;
        }

        # In case the previous click on the canvas was a right-click on an exit, we no longer need
        #   the coordinates of the click
        $self->ivUndef('exitClickXPosn');
        $self->ivUndef('exitClickYPosn');

        # Get the coordinates on the map of the clicked pixel. If the map is magnified we might get
        #   fractional values, so we need to use int()
        ($clickXPosPixels, $clickYPosPixels) = (int($event->x), int($event->y));

        # For mouse button clicks, get the click type and whether or not the SHIFT and/or CTRL keys
        #   were held down
        ($clickType, $button, $shiftFlag, $ctrlFlag) = $self->checkMouseClick($event);
        if (! $clickType) {

            # Not an event in which we're interested
            return undef;
        }

        # Work out which gridblock is underneath the mouse click
        ($clickXPosBlocks, $clickYPosBlocks) = $self->findGridBlock(
            $clickXPosPixels,
            $clickYPosPixels,
            $self->currentRegionmap,
        );

        # If $self->freeClickMode and/or $self->bgColourMode aren't set to 'default', left-clicking
        #   on empty space causes something unusual to happen
        if (
            $clickType eq 'single'
            && $button eq 'left'
            && ($self->freeClickMode ne 'default' || $self->bgColourMode ne 'default')
        ) {
            # Free click mode 'add_room' - 'Add room at click' menu option
            # (NB If this code is altered, the equivalent code in ->enableCanvasPopupMenu must also
            #   be altered)
            if ($self->freeClickMode eq 'add_room') {

                # Only add one new room
                $self->reset_freeClickMode();

                $newRoomObj = $self->mapObj->createNewRoom(
                    $self->currentRegionmap,
                    $clickXPosBlocks,
                    $clickYPosBlocks,
                    $self->currentRegionmap->currentLevel,
                );

                # When using the 'Add room at block' menu item, the new room is selected to make it
                #   easier to see where it was drawn
                # To make things consistent, select this new room, too
                $self->setSelectedObj(
                    [$newRoomObj, 'room'],
                    FALSE,              # Select this object; unselect all other objects
                );

            # Free click mode 'connect_exit' - 'Connect exit to click' menu option
            # Free click mode 'merge_room' - 'Merge/move rooms' menu option
            } elsif (
                $self->freeClickMode eq 'connect_exit'
                || $self->freeClickMode eq 'merge_room'
            ) {
                # If the user has selected the either of these menu option, $self->freeClickMode has
                #   been set and we're waiting for a click on a room; since this part of the grid is
                #   not occupied by a room, we can cancel it now
                $self->reset_freeClickMode();

            # Free click mode 'add_label' - 'Add label at click' menu option
            } elsif ($self->freeClickMode eq 'add_label') {

                $self->addLabelAtClickCallback($clickXPosPixels, $clickYPosPixels);

                # Only add one new label
                $self->reset_freeClickMode();

            # Free click mode 'move_room' - 'Move selected rooms to click' menu option
            } elsif ($self->freeClickMode eq 'move_room') {

                $self->moveRoomsToClick($clickXPosBlocks, $clickYPosBlocks);

                # Only do it once
                $self->reset_freeClickMode();

            # Background colour mode 'square_start' (no menu option)
            } elsif ($self->freeClickMode eq 'default' && $self->bgColourMode eq 'square_start') {

                $self->setColouredSquare($clickXPosBlocks, $clickYPosBlocks);

            # Background colour mode 'rect_start' (no menu option)
            } elsif ($self->freeClickMode eq 'default' && $self->bgColourMode eq 'rect_start') {

                # Store the coordinates of the click, and wait for the second click
                $self->ivPoke('bgColourMode', 'rect_stop');
                $self->ivPoke('bgRectXPos', $clickXPosBlocks);
                $self->ivPoke('bgRectYPos', $clickYPosBlocks);

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                $clickXPosPixels,
                $clickYPosPixels,
                $roomObj,
                $self->currentRegionmap,
            );

            if ($exitObj) {

                if ($button eq 'left' && $event->state =~ m/mod5-mask/) {

                    # This is a drag operation on the nearby exit
                    $listRef = $self->currentParchment->getDrawnExit($exitObj);
                    if (defined $listRef) {

                        $self->startDrag(
                            'exit',
                            $$listRef[0],        # The exit's canvas object
                            $exitObj,
                            $event,
                            $clickXPosPixels,
                            $clickYPosPixels,
                        );
                    }

                } elsif ($button eq 'left') {

                    # If this exit (and/or its twin) is a selected exit, unselect them
                    $result = $self->unselectObj($exitObj);
                    if ($exitObj->twinExit) {

                        $twinExitObj
                            = $self->worldModelObj->ivShow('exitModelHash', $exitObj->twinExit);

                        if ($twinExitObj) {

                            $result2 = $self->unselectObj($twinExitObj);
                        }
                    }

                    if (! $result && ! $result2) {

                        # The exit wasn't already selected, so select it
                        $self->setSelectedObj(
                            [$exitObj, 'exit'],
                            # Retain other selected objects if CTRL key held down
                            $ctrlFlag,
                        );
                    }

                } elsif ($button eq 'right') {

                    # Select the exit, unselecting all other selected objects
                   $self->setSelectedObj(
                        [$exitObj, 'exit'],
                        FALSE,          # Select this object; unselect all other objects
                    );

                    # Create the popup menu
                    if ($self->selectedExit) {

                        $popupMenu = $self->enableExitsPopupMenu();
                        if ($popupMenu) {

                            $popupMenu->popup(
                                undef, undef, undef, undef,
                                $event->button,
                                $event->time,
                            );
                        }
                    }
                }

                return 1;
            }
        }

        # Otherwise, the user clicked in open space

        # If it was a right-click, open a popup menu
        if ($clickType eq 'single' && $button eq 'right') {

            $popupMenu = $self->enableCanvasPopupMenu(
                $clickXPosPixels,
                $clickYPosPixels,
                $clickXPosBlocks,
                $clickYPosBlocks,
            );

            if ($popupMenu) {

                $popupMenu->popup(
                    undef, undef, undef, undef,
                    $event->button,
                    $event->time,
                );
            }

        # If it was a left-click, it's potentially a selection box operation
        } elsif ($clickType eq 'single' && $button eq 'left') {

            # The selection box isn't actually drawn until the user moves their mouse. If they
            #   release the button instead, at that point we unselect all selected objects
            $self->startSelectBox($clickXPosPixels, $clickYPosPixels);

        # If it's a mouse button release, handle the end of any selection box operation
        } elsif ($clickType eq 'release' && $button eq 'left' && $self->selectBoxFlag) {

            $self->stopSelectBox($event, $clickXPosPixels, $clickYPosPixels);

        # Otherwise, if it's a button click (not a button release), just unselect all selected
        #   objects
        } elsif ($clickType ne 'release') {

            $self->setSelectedObj();
        }

        return 1;
    }

    sub canvasObjEventHandler {

        # Handles events on canvas object (i.e. clicking on a room, room tag, room guild, exit,
        #   exit tag or label). Note that clicks on canvas objects for checked directions are
        #   ignored; they are not handled by this function nor by $self->canvasEventHandler
        # The calling function, an anonymous sub defined in $self->setupCanvasObjEvent, filters out
        #   the signals we don't want
        # At the moment, the signals let through the filter are:
        #   button_press, 2button_press
        #
        # Expected arguments
        #   $objType    - 'room', 'room_tag', 'room_guild', 'exit', 'exit_tag' or 'label'
        #   $canvasObj  - The canvas object which intercepted an event signal
        #   $modelObj   - The GA::ModelObj::Room, GA::Obj::Exit or GA::Obj::MapLabel which is
        #                   represented by this canvas object
        #   $event      - The Gtk3::Gdk::Event that caused the signal
        #
        # Return values
        #   'undef' on improper arguments or if the signal $event is one that this function doesn't
        #       handle
        #   1 otherwise

        my ($self, $objType, $canvasObj, $modelObj, $event, $check) = @_;

        # Local variables
        my (
            $clickType, $button, $shiftFlag, $ctrlFlag, $selectFlag, $clickTime, $otherRoomObj,
            $startX, $stopX, $startY, $stopY, $result, $twinExitObj, $result2, $popupMenu,
        );

        # Check for improper arguments
        if (
            ! defined $objType || ! defined $canvasObj || ! defined $modelObj || ! defined $event
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->canvasObjEventHandler', @_);
        }

        # In case the previous click on the canvas was a right-click on an exit, we no longer need
        #   the coordinates of the click
        $self->ivUndef('exitClickXPosn');
        $self->ivUndef('exitClickYPosn');

        # If $self->freeClickMode has been set to 'add_room' or 'add_label' by the 'Add room at
        #   click' or 'Add label at click' menu options, since this part of the grid is already
        #   occupied, we can go back to normal
        if ($self->freeClickMode eq 'add_room' || $self->freeClickMode eq 'add_label') {

            $self->reset_freeClickMode();
        }

        # For mouse button clicks, get the click type and whether or not the SHIFT and/or CTRL keys
        #   were held down
        ($clickType, $button, $shiftFlag, $ctrlFlag) = $self->checkMouseClick($event);
        if (! $clickType) {

            # Not an event in which we're interested
            return undef;
        }

        # Various parts of the function check that these hashes contain at least one item between
        #   them
        if (
            $self->selectedRoomHash || $self->selectedRoomTagHash || $self->selectedRoomGuildHash
            || $self->selectedExitHash || $self->selectedExitTagHash || $self->selectedLabelHash
        ) {
            $selectFlag = TRUE;
        }

        # For capturing double-clicks on rooms, we need to compare the times at which each click is
        #   received
        $clickTime = $axmud::CLIENT->getTime();

        # Process single left clicks
        if ($clickType eq 'single' && $button eq 'left') {

            # Process a left-clicked room differently, if ->freeClickMode has been set to
            #   'connect_exit' by the 'Connect to click' menu option (ignoring the SHIFT/CTRL keys)
            if ($self->freeClickMode eq 'connect_exit' && $objType eq 'room') {

                # Occasionally get an error, when there's no selected exit. $self->freeClickMode
                #   should get reset, but not in these situations
                if (! $self->selectedExit) {

                    $self->reset_freeClickMode();

                } else {

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                }
            }

        # Process right-clicks
        } elsif ($clickType eq 'single' && $button eq 'right') {

            if ($objType eq 'exit') {

                # For twin exits - which share a canvas object - use the exit whose parent room is
                #   closest to the click
                $modelObj = $self->chooseClickedExit($modelObj, int($event->x), int($event->y));
            }

            # If a group of things are already selected, unselect them all and select the object
            #   that was clicked
            if ($selectFlag) {

                # Select this room/label, unselecting all other objects
                $self->setSelectedObj(
                    [$modelObj, $objType],
                    # Retain other selected objects if CTRL key held down
                    $ctrlFlag,
                );

            } else {

                # If this object isn't already selected, select it (but don't unselect something
                #   as we would for a left-click)
                if ($objType eq 'room_tag') {

                    $self->setSelectedObj(
                        [$modelObj, 'room_tag'],
                        FALSE,          # Select this object; unselect all other objects
                    );

                } elsif ($objType eq 'room_guild') {

                    $self->setSelectedObj(
                        [$modelObj, 'room_guild'],
                        FALSE,          # Select this object; unselect all other objects
                    );

                } elsif ($objType eq 'exit_tag') {

                    $self->setSelectedObj(
                        [$modelObj, 'exit_tag'],
                        FALSE,          # Select this object; unselect all other objects
                    );

                } else {

                    $self->setSelectedObj(
                        [$modelObj, $objType],
                        FALSE,          # Select this object; unselect all other objects
                    );
                }
            }

            # Create the popup menu
            if ($objType eq 'room' && $self->selectedRoom) {
                $popupMenu = $self->enableRoomsPopupMenu();
            } elsif ($objType eq 'room_tag' && $self->selectedRoomTag) {
                $popupMenu = $self->enableRoomTagsPopupMenu();
            } elsif ($objType eq 'room_guild' && $self->selectedRoomGuild) {
                $popupMenu = $self->enableRoomGuildsPopupMenu();
            } elsif ($objType eq 'exit_tag' && $self->selectedExitTag) {
                $popupMenu = $self->enableExitTagsPopupMenu();
            } elsif ($objType eq 'exit' && $self->selectedExit) {

                # Store the position of the right-click, in case the user wants to add a bend from
                #   the popup menu
                $self->ivPoke('exitClickXPosn', int($event->x));
                $self->ivPoke('exitClickYPosn', int($event->y));
                # Now we can open the poup menu
                $popupMenu = $self->enableExitsPopupMenu();

            } elsif ($objType eq 'label' && $self->selectedLabel) {

                $popupMenu = $self->enableLabelsPopupMenu();
            }

            if ($popupMenu) {

                $popupMenu->popup(undef, undef, undef, undef, $event->button, $event->time);
            }
        }

        return 1;
    }

    sub deleteCanvasObj {

        # Called by numerous functions
        #
        # When a region object, room object, room tag, room guild, exit, exit tag or label is being
        #   drawn, redrawn or deleted from the world model, this function must be called
        # The function checks whether the model object is currently drawn on a map as one or more
        #   canvas objects and, if it is, destroys the canvas objects
        #
        # This function also handles coloured blocks and rectangles on the background map, details
        #   of which are stored in the regionmap object (GA::Obj::Regionmap), not the world model
        # The function checks whether a canvas object for the coloured block/rectangle is currently
        #   displayed on the map as a canvas object and, if so, destroys the canvas object
        #
        # Expected arguments
        #   $type       - Set to 'region', 'room', 'room_tag', 'room_guild', 'exit', 'exit_tag' or
        #                   'label' for world model objects, 'checked_dir' for checked directions
        #                   and 'square', 'rect' for coloured blocks/rectangles
        #   $modelObj   - The GA::ModelObj::Region, GA::ModelObj::Room, GA::Obj::Exit or
        #                   GA::Obj::MapLabel being drawn /redrawn / deleted
        #               - For checked directions, the GA::ModelObj::Room in which the checked
        #                   direction is stored
        #               - For coloured squares, it's not a blessed reference, but a coordinate in
        #                   the form 'x_y' (to delete canvas objects on all levels), or 'x_y_z' (to
        #                   delete the canvas object on one level)
        #               - For coloured rectangles, it's not a blessed reference, but a key in the
        #                   form 'object-number' (to delete canvas objects on all levels), or
        #                   'object-number_level' (to delete the canvas object on one level)
        #
        # Optional arguments
        #   $regionmapObj, $parchmentObj
        #               - The regionmap and parchment object for $modelObj. If not set, this
        #                   function fetches them. Both must be specified if $type is 'square' or
        #                   'rect')
        #   $deleteFlag - Set to TRUE if the object is being deleted from the world model, FALSE
        #                   (or 'undef') if not. Never TRUE for coloured blocks/rectangles which
        #                   are not stored in the world model
        #
        # Return values
        #   'undef' on improper arguments, if there's an error or if there are no canvas objects to
        #       destroy
        #   1 otherwise

        my ($self, $type, $modelObj, $regionmapObj, $parchmentObj, $deleteFlag, $check) = @_;

        # Local variables
        my (
            $roomObj,
            @redrawList,
            %redrawHash,
        );

        # Check for improper arguments
        if (! defined $type || ! defined $modelObj || defined $check) {

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        # Expected arguments
        #   $clickRoomObj   - The room that was left-clicked by the user
        #
        # Return values
        #   'undef' on improper arguments or if none of the buttons in the quick painting toolbar
        #       are selected
        #   1 otherwise

        my ($self, $clickRoomObj, $check) = @_;

        # Local variables
        my @roomList;

        # Check for improper arguments
        if (! defined $clickRoomObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->doQuickPaint', @_);
        }

        # Do nothing if none of the colour buttons in the quick painting toolbar are selected
        if (! $self->toolbarQuickPaintColour) {

            return undef;
        }

        # If the room is a selected room, room flags should be toggled in all rooms
        if (
            ($self->selectedRoom && $self->selectedRoom eq $clickRoomObj)
            || $self->ivExists('selectedRoomHash', $clickRoomObj->number)
        ) {
            push (@roomList, $self->compileSelectedRooms());

        } else {

            # Just toggle room flags in the clicked room
            push (@roomList, $clickRoomObj);
        }

        # Toggle the room flag in those rooms
        $self->worldModelObj->toggleRoomFlags(
            $self->session,
            TRUE,                               # Update automapper windows
            $self->toolbarQuickPaintColour,
            @roomList,
        );

        if (! $self->worldModelObj->quickPaintMultiFlag) {

            # User wants the choice of room flag to reset after clicking on a room
            $self->ivUndef('toolbarQuickPaintColour');
        }

        return 1;
    }

    sub doMerge {

        # Called by $self->canvasEventHandler when a double-click is detected on the current room
        #   and the automapper object is set up to perform a merge (i.e.
        #   GA::Map::Obj->currentMatchFlag is TRUE)
        # Also called by $self->enableRoomsColumnm ->enableRoomsPopupMenu and
        #   ->canvasObjEventHandler
        #
        # Prepares a call to GA::Obj::WorldModel->mergeMap, then makes the call
        #
        # Expected arguments
        #   $currentRoomObj - The room that is definitely to be merged with another room (at the
        #                       moment, it's always the automapper object's current room)
        #
        # Optional arguments
        #   $targetRoomObj  - When called by $self->canvasObjEventHandler, because
        #                       GA::Obj::Map->currentMatchList specifies several rooms that match
        #                       the current room, then this variable is the room that was clicked
        #   $noConfirmFlag  - TRUE if the confirmation dialogue window should not be shown; FALSE
        #                       (or 'undef') if it should be shown as usual
        #
        # Return values
        #   'undef' on improper arguments, if the user declines to perform the operation after a
        #       prompt or if the merge operation fails
        #   1 otherwise

        my ($self, $currentRoomObj, $targetRoomObj, $noConfirmFlag, $check) = @_;

        # Local variables
        my (
            $autoRescueFlag, $regionmapObj, $response,
            @selectList, @otherRoomList, @labelList,
        );

        # Check for improper arguments
        if (! defined $currentRoomObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->doMerge', @_);
        }

        if ($self->mapObj->ivNumber('currentMatchList') > 1 && ! $targetRoomObj) {

            # GA::Obj::Map->currentMatchList specifies multiple rooms which match the current
            #   room (which are all selected), so allow the user to click on one of those rooms,
            #   after which this function is called again by $self->canvasObjEventHandler
            $self->set_freeClickMode('merge_room');

            # After a double-click on the current room, none of the matching rooms will be selected,
            #   so select them again
            if (! $self->selectedRoom && ! $self->selectedRoomHash) {

                foreach my $roomObj ($self->mapObj->currentMatchList) {

                    push (@selectList, $roomObj, 'room');
                }

                $self->setSelectedObj(\@selectList, TRUE);
            }

        } else {

            # If $targetRoomObj was set by the calling function, then GA::Obj::Map->currentMatchList
            #   specifies multiple rooms which match the current room, and the user has clicked one
            #   of them
            if (! $targetRoomObj) {

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

            if ($modelObj->destRoom) {

                $destRoomObj = $self->worldModelObj->ivShow('modelHash', $modelObj->destRoom);
                $label .= "\n  Destination room #" . $destRoomObj->number;
            }

            if ($modelObj->twinExit) {

                $twinExitObj = $self->worldModelObj->ivShow('exitModelHash', $modelObj->twinExit);
                $label .= "\n  Twin exit #" . $twinExitObj->number . " \'" . $twinExitObj->dir
                            . "\'";
            }

            if (defined $modelObj->altDir) {

                $label .= "\n  Alternative directions:\n    " . $modelObj->altDir;
            }

            if ($modelObj->exitInfo) {

                $label .= "\n  Info: " . $modelObj->exitInfo;
            }

        } elsif ($type eq 'exit_tag') {

            $label = "Exit tag \'" . $modelObj->exitTag . "\'";
            $label .= "\n  Exit #" . $modelObj->number . " \'" . $modelObj->dir . "\'";

        } elsif ($type eq 'label') {

            $label = "Label #" . $modelObj->number;
            # Convert the label's coordinates in pixels to gridblocks
            $xPos = int($modelObj->xPosPixels / $self->currentRegionmap->blockWidthPixels);
            $yPos = int($modelObj->yPosPixels / $self->currentRegionmap->blockHeightPixels);
            $label .= " (" . $xPos . ", " . $yPos . ", " . $modelObj->level . ")";

            if (! defined $modelObj->style) {
                $label .= "\nStyle: <custom>";
            } else {
                $label .= "\nStyle: \'" . $modelObj->style . "\'";
            }

            # (The text can include a lot of empty space and newline characters, so strip all of
            #   that)
            $modText = $modelObj->name;
            $modText =~ s/^[\s\n]*//;
            $modText =~ s/[\s\n]*$//;
            $modText =~ s/[\s\n]+/ /g;

            $label .= "\nText: \'" . $modText . "\'";

        } else {

            # Failsafe: empty string
            $label = "";
        }

        return $label;
    }

    # Menu 'File' column callbacks

    sub importModelCallback {

        # Called by $self->enableFileColumn
        # Imports a world model file specified by the user and (if successful) loads it into memory
        #   (a combination of ';importfiles' and ';load -m')
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->importModelCallback', @_);
        }

        # (No standard callback checks for this function)

        # Watch out for file operation failures
        $axmud::CLIENT->set_fileFailFlag(FALSE);
        # Allow a world model, associated with a world profile with a different name, to be imported
        #   into the current world's file structures (but only if the archive file contains only a
        #   world model)
        $self->session->set_transferWorldModelFlag(TRUE);

        # Import a file, specified by the user
        if (
            $self->session->pseudoCmd('importfiles')
            && ! $axmud::CLIENT->fileFailFlag
        ) {
            # The world model data has been incorporated into Axmud's data files, but not loaded
            #   into memory. Load it into memory now
            if (
                $self->session->pseudoCmd('load -m')
                && ! $axmud::CLIENT->fileFailFlag
            ) {
                # Make sure the world model object has the right parent world set, after the file
                #   import
                $self->session->worldModelObj->{_parentWorld} = $self->session->currentWorld->name;
                # Save the world model, to make sure the file has the right parent world set, too
                $self->session->pseudoCmd('save -f -m');
            }
        }

        # Reset the flag
        $self->session->set_transferWorldModelFlag(FALSE);

        return 1;
    }

    sub exportModelCallback {

        # Called by $self->enableFileColumn
        # Saves the current world model and (if successful) exports the 'worldmodel' file to a

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($fileObj, $choice);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->exportModelCallback', @_);
        }

        # (No standard callback checks for this function)

        # If the world model data in memory is unsaved, prompt whether to save it first
        $fileObj = $self->session->ivShow('sessionFileObjHash', 'worldmodel');
        if ($fileObj && $fileObj->modifyFlag) {

            # Watch out for file operation failures
            $axmud::CLIENT->set_fileFailFlag(FALSE);

            # Prompt the user
            $choice = $self->showMsgDialogue(
                'Unsaved world model',
                'question',
                'The world model in memory is not saved. Do you want to save it before exporting?'
                . ' (If you choose \'No\', the previously saved world model file will be exported'
                . ' instead)',
                'yes-no',
            );

            if ($choice eq 'yes') {

                # Save the world model
                $self->session->pseudoCmd('save -m', 'win_error');

                if ($axmud::CLIENT->fileFailFlag) {

                    # Something went wrong; don't attempt to export anything
                    return 1;
                }
            }
        }

        # Export the world model data file
        $self->session->pseudoCmd(
            'exportfiles -m ' . $self->session->currentWorld->name,
            'win_error',
        );

        return 1;
    }

    # Menu 'Edit' column callbacks

    sub selectInRegionCallback {

        # Called by $self->enableEditColumn
        # Selects rooms, exits, room tags, room guilds or labels (or everything) in the current
        #   region
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   $type   - Set to 'room', 'exit', 'room_tag', 'room_guild', or 'label'. If not defined,
        #               selects everything
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $type, $check) = @_;

        # Local variables
        my (
            $count,
            @roomList, @exitList, @roomTagList, @roomGuildList, @labelList,
            %roomHash, %exitHash, %roomTagHash, %roomGuildHash, %labelHash,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->selectInRegionCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap) {

            return undef;
        }

        # Make sure there are no rooms, exits, room tags or labels selected
        $self->ivUndef('selectedRoom');
        $self->ivEmpty('selectedRoomHash');
        $self->ivUndef('selectedExit');
        $self->ivEmpty('selectedExitHash');
        $self->ivUndef('selectedRoomTag');
        $self->ivEmpty('selectedRoomTagHash');
        $self->ivUndef('selectedRoomGuild');
        $self->ivEmpty('selectedRoomGuildHash');
        $self->ivUndef('selectedLabel');
        $self->ivEmpty('selectedLabelHash');

        # Select all rooms, exits, room tags, room guilds and/or labels
        if (! defined $type || $type eq 'room') {

            # $self->currentRegionmap->gridRoomHash contains all the rooms in the regionmap
            # Get a list of world model numbers for each room
            @roomList = $self->currentRegionmap->ivValues('gridRoomHash');
        }

        if (! defined $type || $type eq 'exit') {

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                    my $roomObj = $self->worldModelObj->ivShow('modelHash', $roomNum);
                    $roomCount++;

                    foreach my $char ($roomObj->ivKeys('visitHash')) {

                        my $profObj = $self->session->ivShow('profHash', $char);

                        if (! $profObj || $profObj->category ne 'char') {

                            # The character which visited this room no longer exists as a character
                            #   profile
                            $self->worldModelObj->resetVisitCount(
                                TRUE,       # Update Automapper windows now
                                $roomObj,
                                $char,
                            );

                            $deleteCount++;
                        }
                    }
                }
            }

        # Deal with profile characters
        } else {

            foreach my $regionmapObj (@regionList) {

                foreach my $roomNum ($regionmapObj->ivValues('gridRoomHash')) {

                    my $roomObj = $self->worldModelObj->ivShow('modelHash', $roomNum);
                    $roomCount++;

                    foreach my $char (@charList) {

                        if ($roomObj->ivExists('visitHash', $char)) {

                            # Remove this character's visits from the room
                            $self->worldModelObj->resetVisitCount(
                                TRUE,       # Update Automapper windows now
                                $roomObj,
                                $char,
                            );

                            $deleteCount++;
                        }
                    }
                }
            }
        }

        # Show confirmation
        return $self->showMsgDialogue(
            'Reset character visits',
            'info',
            'Operation complete (rooms: ' . $roomCount . ', records deleted: ' . $deleteCount . ')',
            'ok',
        );
    }

    # Menu 'View' column callbacks

    sub changeCharDrawnCallback {

        # Called by $self->enableViewColumn
        # In GA::Obj::WorldModel->roomInteriorMode 'visit_count', changes which character's visits
        #   are drawn
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $currentString, $choice, $redrawFlag, $choiceObj,
            @profList, @sortedList, @comboList,
            %comboHash,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->changeCharDrawnCallback',
                @_,
            );
        }

        # (No standard callback checks for this function)

        # Get a sorted list of character profiles, not including the current character (if any)
        foreach my $profObj ($self->session->ivValues('profHash')) {

            if (
                $profObj->category eq 'char'
                && (! $self->session->currentChar || $self->session->currentChar ne $profObj)
            ) {
                push (@profList, $profObj);
            }
        }

        @sortedList = sort {lc($a->name) cmp lc($b->name)} (@profList);

        # Prepare a list to show in a combo box. At the same time, compile a hash in the form:
        #   $hash{combo_box_string} = blessed_reference_of_equivalent_profile
        foreach my $profObj (@sortedList) {

            push (@comboList, $profObj->name);
            $comboHash{$profObj->name} = $profObj;
        }

        # Add the current character (if there is one) to top of the combo
        if ($self->session->currentChar) {

            $currentString = '<Use current character>';
            unshift (@comboList, $currentString);

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN


        # Set the new magnification; the called function updates every Automapper window using the
        #   current worldmodel
        $self->worldModelObj->setMagnification($self, $newMag);

        return 1;
    }

    sub changeLevelCallback {

        # Called by $self->enableViewColumn
        # Prompts the user for a new level in the current regionmap, then sets it as the currently-
        #   displayed level
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $level;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->changeLevelCallback', @_);
        }

        # (No standard callback checks for this function)

        # Prompt the user for a new level
        $level = $self->showEntryDialogue(
            'Change level',
            'Enter the new level number (\'ground\' level is 0)',
        );

        if (defined $level) {

            # Check that $level is a valid integer (positive, negative or 0)
            if (! ($level =~ m/^-?\d+$/)) {

                return $self->showMsgDialogue(
                    'Change level',
                    'error',
                    'Invalid level \'' . $level . '\' - you must use an integer',
                    'ok',
                );
            }

            # Set the new current level, which redraws the map
            $self->setCurrentLevel($level);
        }

        return 1;
    }

    # Menu 'Mode' column callbacks

    sub verboseCharsCallback {

        # Called by $self->enableModeColumn
        # Sets the number of characters at the beginning of a verbose description that are checked
        #   to match a world model room with the Locator's current room
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $number;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->verboseCharsCallback', @_);
        }

        # (No standard callback checks for this function)

        # Prompt for a new number of verbose characters to match
        $number = $self->showEntryDialogue(
            'Match verbose description',
            'Enter number of initial characters to match (0 = match whole description)',
            undef,
            $self->worldModelObj->matchDescripCharCount,
        );

        if ($axmud::CLIENT->intCheck($number, 0)) {

            $self->worldModelObj->set_matchDescripCharCount($number);
        }

        return 1;
    }

    sub repaintSelectedRoomsCallback {

        # Called by $self->enableModeColumn
        # 'Repaints' the selected room(s) by copying the values of certain IVs stored in the world
        #   model's painter object (a non-model GA::ModelObj::Room) to each selected room
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (@roomList, @redrawList);

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN


        # Prompt for a new maximum
        $number = $self->showEntryDialogue(
            'Set limit on room comparisons',
            'When comparing the Locator task\'s current room against rooms in the model, set the'
            . ' maximum number of rooms to compare (0 - no limit)',
            undef,
            $self->worldModelObj->autoCompareMax,
        );

        if ($axmud::CLIENT->intCheck($number, 0)) {

            $self->worldModelObj->set_autoCompareMax($number);
        }

        return 1;
    }

    sub autoSlideMaxCallback {

        # Called by $self->enablemodecolumn
        # Sets the maximum distance for auto-slide operations
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $number;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->autoSlideMaxCallback', @_);
        }

        # (No standard callback checks for this function)

        # Prompt for a new maximum
        $number = $self->showEntryDialogue(
            'Set limit on slide distance',
            'When sliding a new room into an unoccupied gridblock, set the maximum slide distance'
            . ' (minimum value: 1)',
            undef,
            $self->worldModelObj->autoSlideMax,
        );

        if ($axmud::CLIENT->intCheck($number, 1)) {

            $self->worldModelObj->set_autoSlideMax($number);
        }

        return 1;
    }

    # Menu 'Regions' column callbacks

    sub newRegionCallback {

        # Called by $self->enableRegionsColumn
        # Adds a new region to the world model
        #
        # Expected arguments
        #   $tempFlag   - If set to TRUE, the new region is a temporary region (that should be
        #                   deleted, the next time the world model is loaded from file)
        #
        # Return values
        #   'undef' on improper arguments, if the new model object can't be created or if the user
        #       cancels the operation
        #   1 otherwise

        my ($self, $tempFlag, $check) = @_;

        # Local variables
        my (
            $successFlag, $name, $parentName, $width, $height, $parentNumber, $regionObj, $title,
            $regionmapObj,
        );

        # Check for improper arguments
        if (! defined $tempFlag || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->newRegionCallback', @_);
        }

        # (No standard callback checks for this function)

        # Prompt the user for a region name, parent region name and map size
        ($successFlag, $name, $parentName, $width, $height) = $self->promptNewRegion($tempFlag);
        if (! $successFlag) {

            # User cancelled the operation
            return undef;

        } else {

            # Check the name is not already in use
            if (defined $name && $self->worldModelObj->ivExists('regionmapHash', $name)) {

                if ($tempFlag) {
                    $title = 'New temporary region';
                } else {
                    $title = 'New region';
                }

                $self->showMsgDialogue(
                    $title,
                    'error',
                    'There is already a region called \'' . $name . '\'',
                    'ok',
                );

                return undef;
            }

            # If a parent was specified, find its world model number

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                'The world model doesn\'t contain any temporary regions',
                'ok',
            );

            return undef;

        } else {

            # Give the user a chance to change their minds, before emptying the region
            if (@tempList == 1) {

                $msg = 'There is 1 temporary region in the world model. Are you sure you want to'
                            . ' delete it?';
            } else {

                $msg = 'There are ' . scalar @tempList . ' temporary regions in the world model.'
                            . ' Are you sure you want to delete them all?'
            }

            $result = $self->showMsgDialogue(
                'Delete temporary regions',
                'question',
                $msg,
                'yes-no',
            );

            if ($result ne 'yes') {

                return undef;
            }
        }

        # Work out roughly how many rooms and labels will be deleted. If it's a lot, show a pause
        #   window
        $total = 0;
        foreach my $regionObj (@tempList) {

            my $regionmapObj = $self->worldModelObj->ivShow('regionmapHash', $regionObj->name);

            $total += $regionmapObj->ivPairs('gridRoomHash');
            $total += $regionmapObj->ivPairs('gridLabelHash');
        }

        if ($total > $self->worldModelObj->drawPauseNum) {

            $self->showPauseWin();
        }

        # Delete each temporary region in turn
        $self->worldModelObj->deleteTempRegions(
            $self->session,
            TRUE,              # Update Automapper windows now
        );

        # Make the pause window invisible
        $self->hidePauseWin();

        return 1;
    }

    # Menu 'Rooms' column callbacks

    sub resetLocatorCallback {

        # Called by $self->enableRoomsColumn
        # Resets the Locator task, and marks the automapper as lost
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->resetLocatorCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap) {

            return undef;
        }

        # Reset the Locator task
        $self->session->pseudoCmd('resetlocatortask', $self->pseudoCmdMode);

        # The call to ;resetlocatortask should mark the automapper as lost - but, if it's not, do it
        #   from here
        if ($self->mapObj->currentRoom) {

            return $self->mapObj->setCurrentRoom();
        }

        # Display an explanatory message, if necessary
        if ($self->worldModelObj->explainGetLostFlag) {

            $self->session->writeText('MAP: Lost because of a Locator reset');
        }

        return 1;
    }

    sub setFacingCallback {

        # Called by $self->enableRoomsColumn
        # Sets the direction the character is facing
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        # (For convenience, put the longest directions at the end)
        @longList = qw(
            north northeast east southeast south southwest west northwest up down
            northnortheast eastnortheast eastsoutheast southsoutheast
            southsouthwest westsouthwest westnorthwest northnorthwest
        );

        if ($self->worldModelObj->showAllPrimaryFlag) {
            @dirList = @longList;
        } else {
            @dirList = @shortList;
        }

        # Get a list of (custom) primary directions, in the standard order
        foreach my $key (@dirList) {

            push (@customList, $dictObj->ivShow('primaryDirHash', $key));
        }

        # Prompt the user for a distance and a direction
        ($distance, $choice) = $self->showEntryComboDialogue(
            'Move selected rooms',
            'Enter a distance (in gridblocks)',
            'Select the direction of movement',
            \@customList,
        );

        # If the 'cancel' button was clicked, $distance will be 'undef'. The user might also have
        #   entered the distance 0. In either case, we don't move anything
        if (! $distance) {

            # Operation cancelled
            return undef;

        } else {

            # Check that the distance is a positive integer
            if (! $axmud::CLIENT->intCheck($distance, 1)) {

                # Open a 'dialogue' window to explain the problem
                $self->showMsgDialogue(
                    'Move selected rooms',
                    'error',
                    'The distance must be a positive integer',
                    'ok',
                );

                return undef;
            }

            # $dir is a custom primary direction; convert it into the standard primary direction
            $standardDir = $dictObj->ivShow('combRevDirHash', $choice);

            # Move the selected room(s)
            return $self->moveRoomsInDir($distance, $standardDir);
        }
    }

    sub transferSelectedRoomsCallback {

        # Called by $self->enableEditColumn and ->enableRoomsPopupMenu
        # Transfers the selected rooms (and any selected labels, if there is at least one selected
        #   room) to the same location in a specified region
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   $regionName     - The name of the region into which the rooms/labels should be
        #                       transferred. All selected rooms/labels must be in the same region,
        #                       and that region must not be the same as $regionName. If 'undef', the
        #                       user is prompted to select a region
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the move
        #       operation fails
        #   1 otherwise

        my ($self, $regionName, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->transferSelectedRoomsCallback',
                @_,
            );
        }

        # Standard callback check
        if (! $self->currentRegionmap || (! $self->selectedRoom && ! $self->selectedRoomHash)) {

            return undef;
        }

        # Make sure any free click mode operations, like connecting exits or moving rooms, are
        #   cancelled
        $self->reset_freeClickMode();

        # Prompt the user to select a region, if no region was specified by the calling function
        if (! defined $regionName) {

            # Get a sorted list of region names
            @comboList = sort {lc($a) cmp lc($b)} ($self->worldModelObj->ivKeys('regionmapHash'));

            # Prompt the user for a region name
            $regionName = $self->showComboDialogue(
                'Select region',
                'Select the destination region',
                \@comboList,
            );

            if (! defined $regionName) {

                return undef;
            }
        }

        # Move the selected rooms/labels
        return $self->transferRoomsToRegion($regionName);
    }

    sub compareRoomCallback {

        # Called by $self->->enableRoomsPopupMenu
        # Compares the selected room with rooms in the region or the whole world model, and selects
        #   any matching rooms
        #
        # Expected arguments
        #   $wholeFlag  - FALSE to compare rooms in the same region, TRUE to compare rooms in the
        #                   whole world model
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the move
        #       operation fails
        #   1 otherwise

        my ($self, $wholeFlag, $check) = @_;

        # Local variables
        my (
            $wmObj, $selectObj, $regionmapObj, $string,
            @roomList, @matchList, @selectList,
        );

        # Check for improper arguments
        if (! defined $wholeFlag || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->compareRoomCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap || ! $self->selectedRoom) {

            return undef;
        }

        # Import the world model object and the selected room (for speed)
        $wmObj = $self->worldModelObj;
        $selectObj = $self->selectedRoom;

        # Get a list of rooms which should be compared with the selected room
        if (! $wholeFlag) {

            $regionmapObj = $self->findRegionmap($self->selectedRoom->parent);
            foreach my $roomNum ($regionmapObj->ivValues('gridRoomHash')) {

                push (@roomList, $wmObj->ivShow('modelHash', $roomNum));
            }

        } else {

            push (@roomList, $wmObj->ivValues('roomModelHash'));
        }

        # Compare rooms in each region, one by one
        foreach my $thisObj (@roomList) {

            my $result;

            if ($thisObj ne $selectObj) {

                ($result) = $wmObj->compareRooms($self->session, $selectObj, $thisObj);
                if ($result) {

                    push (@matchList, $thisObj);
                    push (@selectList, $thisObj, 'room');
                }
            }
        }

        if (! @matchList) {

            # Show a confirmation
            return $self->showMsgDialogue(
                'Compare room',
                'error',
                'No matching rooms found',
                'ok',
            );

        } else {

            # Unselect the currently-selected room...
            $self->setSelectedObj();
            # ...so we can select all matching rooms. The TRUE argument means to select multiple
            #   objects
            $self->setSelectedObj(\@selectList, TRUE);

            if ((scalar @matchList) == 1) {
                $string = '1 room';
            } else {
                $string = (scalar @matchList) . ' rooms';
            }

            # Show a confirmation
            return $self->showMsgDialogue(
                'Compare room',
                'info',
                'Found ' . $string . ' matching room #' . $selectObj->number,
                'ok',
            );
        }
    }

    sub executeScriptsCallback {

        # Called by $self->enableRoomsPopupMenu
        # Executes Axbasic scripts for the current room, as if the character had just arrived
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->executeScriptsCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap || ! $self->mapObj->currentRoom) {

            return undef;
        }

        # If there are no Axbasic scripts for the current room, display a warning
        if (! $self->mapObj->currentRoom->arriveScriptList) {

            return $self->showMsgDialogue(
                'Run ' . $axmud::BASIC_NAME . ' scripts',
                'warning',
                'The current room has not been assigned any ' . $axmud::BASIC_NAME . ' scripts',
                'ok',
            );
        }

        # Otherwise, execute the scripts
        foreach my $scriptName ($self->mapObj->currentRoom->arriveScriptList) {

            $self->session->pseudoCmd('runscript ' . $scriptName);
        }

        return 1;
    }

    sub addFirstRoomCallback {

        # Called by $self->enableRoomsColumn. Also called by Axbasic ADDFIRSTROOM function
        # For an empty region, draws a room in the centre of the grid and marks it as the current
        #   room
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails, if the Locator task
        #       isn't running or if it is still expecting room statements or if the new room can't
        #       be created
        #   1 otherwise

        my ($self, $check) = @_;

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                                'Failed to add one or more exits (internal error)',
                                'ok',
                            );

                            return undef;
                        }

                        # Mark it as a hidden exit, if necessary
                        if ($hiddenFlag) {

                            $self->worldModelObj->setHiddenExit(
                                FALSE,          # Don't redraw the map yet...
                                $exitObj,
                                TRUE,           # Exit is now hidden
                            );
                        }

                        # Mark the room to be redrawn
                        push (@drawList, 'room', $roomObj);

                        # Now, if there are any incoming 1-way exits whose ->mapDir is the opposite
                        #   of the exit we've just added, the incoming exit should be marked as an
                        #   uncertain exit
                        $self->worldModelObj->modifyIncomingExits(
                            $self->session,
                            TRUE,              # Redraw any modified incoming exit
                            $roomObj,
                            $exitObj,
                        );
                    }
                }
            }

            # Redraw the selected room(s) in every window
            $self->worldModelObj->updateMaps(@drawList);

            return 1;

        } else {

            # No exits were selected
            return undef;
        }
    }

    sub addFailedExitCallback {

        # Called by $self->enableRoomsColumn
        # When the character fails to move, and it's not a recognised failed exit pattern, the map
        #   gets messed up
        # This is a convenient way to deal with it. Adds a new failed exit string to the current
        #   world profile or to the specified room, and empties the Locator's move list
        #
        # Expected arguments
        #   $worldFlag   - If set to TRUE, a failed exit pattern is added to the world profile. If
        #                   set to FALSE, the pattern is added to the room
        #
        # Optional arguments
        #   $roomObj    - If $worldFlag is FALSE, the room to which the pattern should be added.
        #                   When called by $self->enableRoomsColumn, it will be the current room;
        #                   when called by ->enableRoomsPopupMenu, it will be the selected room
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the user
        #       doesn't supply a pattern
        #   1 otherwise

        my ($self, $worldFlag, $roomObj, $check) = @_;

        # Local variables
        my (
            $pattern, $type, $worldObj, $iv, $descrip, $taskObj,
            @comboList,
        );

        # Check for improper arguments
        if (! defined $worldFlag || (! $worldFlag && ! defined $roomObj) || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->addFailedExitCallback', @_);
        }

        # Standard callback check
        if (
            $roomObj
            && (
                ! $self->currentRegionmap
                || (! $self->mapObj->currentRoom && ! $self->selectedRoom)
            )
        ) {
            return undef;
        }

        if (! $worldFlag) {

            # Prompt the user for a new failed exit pattern to add to the room
            $pattern = $self->showEntryDialogue(
                'Add failed exit to room',
                'Enter a pattern to match the failed exit',
            );

            if (! $pattern) {

                return undef;

            } else {

                $self->worldModelObj->addExitPattern($roomObj, 'fail', $pattern);
            }

        } else {

            # Import the current world profile
            $worldObj = $self->session->currentWorld;

            # Prompt the user for a new failed exit pattern to add to the world profile
            @comboList = ('Closed door', 'Locked door', 'Other failed exit');
            ($pattern, $type) = $self->showEntryComboDialogue(
                'Add failed exit to world',
                'Enter a pattern to match the failed exit',
                'Which kind of failed exit was it?',
                \@comboList,

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                return undef;

            } else {

                # Check that the pattern isn't already in the list
                if ($type eq 'Closed door') {

                    $iv = 'doorPatternList';
                    $descrip = 'a closed door pattern';

                } elsif ($type eq 'Locked door') {

                    $iv = 'lockedPatternList';
                    $descrip = 'a locked door pattern';

                } else {
                    $iv = 'failExitPatternList';
                    $descrip = 'a failed exit pattern';
                }

                if ($worldObj->ivMatch($iv, $pattern)) {

                    $self->showMsgDialogue(
                        'Add failed exit to world',
                        'error',
                        'The current world profile already has ' . $descrip . ' pattern matching \''
                        . $pattern . '\'',
                        'ok',
                    );

                    return undef;

                } else {

                    # Add the pattern
                    $worldObj->ivPush($iv, $pattern);
                }
            }
        }

        # Import the Locator task
        $taskObj = $self->session->locatorTask;
        if ($taskObj) {

            # Empty the Locator's move list IVs and update its task window
            $taskObj->resetMoveList();
        }

        return 1;
    }

    sub addInvoluntaryExitCallback {

        # Called by $self->enableRoomsColumn
        # This callback adds an involuntary exit pattern to the specified room and empties the
        #   Locator task's move list
        #
        # Expected arguments
        #   $roomObj    - The room to which the pattern should be added. When called by
        #                   $self->enableRoomsColumn, it will be the current room; when called by
        #                   ->enableRoomsPopupMenu, it will be the selected room
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the user
        #       doesn't supply a pattern
        #   1 otherwise

        my ($self, $roomObj, $check) = @_;

        # Local variables
        my ($pattern, $otherVal, $taskObj);

        # Check for improper arguments
        if (! defined $roomObj || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->addInvoluntaryExitCallback',
                @_,
            );
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || (! $self->mapObj->currentRoom && ! $self->selectedRoom)
        ) {
            return undef;
        }

        # Prompt the user for a new involuntary exit pattern to add to the room
        ($pattern, $otherVal) = $self->showDoubleEntryDialogue(
            'Add involuntary exit to room',
            'Enter a pattern to match the involuntary exit',
            '(Optional) add a direction or a destination room',
        );

        if (! defined $pattern || $pattern eq '') {

            return undef;

        } else {

            # Use 'undef' rather than an empty string
            if ($otherVal eq '') {

                $otherVal = undef;
            }

            $self->worldModelObj->addInvoluntaryExit($roomObj, $pattern, $otherVal);

            # Import the Locator task
            $taskObj = $self->session->locatorTask;
            if ($taskObj) {

                # Empty the Locator's move list IVs and update its task window
                $taskObj->resetMoveList();
            }
        }

        return 1;
    }

    sub addRepulseExitCallback {

        # Called by $self->enableRoomsColumn
        # This callback adds a repulse exit pattern to the specified room and empties the Locator
        #   task's move list
        #
        # Expected arguments
        #   $roomObj    - The room to which the pattern should be added. When called by
        #                   $self->enableRoomsColumn, it will be the current room; when called by
        #                   ->enableRoomsPopupMenu, it will be the selected room
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the user
        #       doesn't supply a pattern
        #   1 otherwise

        my ($self, $roomObj, $check) = @_;

        # Local variables
        my ($pattern, $otherVal, $taskObj);

        # Check for improper arguments
        if (! defined $roomObj || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->addRepulseExitCallback',
                @_,
            );
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || (! $self->mapObj->currentRoom && ! $self->selectedRoom)
        ) {
            return undef;
        }

        # Prompt the user for a new repulse exit pattern to add to the room
        ($pattern, $otherVal) = $self->showDoubleEntryDialogue(
            'Add repulse exit to room',
            'Enter a pattern to match the repulse exit',
            '(Optional) add a direction or a destination room',
        );

        if (! defined $pattern || $pattern eq '') {

            return undef;

        } else {

            # Use 'undef' rather than an empty string
            if ($otherVal eq '') {

                $otherVal = undef;
            }

            $self->worldModelObj->addRepulseExit($roomObj, $pattern, $otherVal);

            # Import the Locator task
            $taskObj = $self->session->locatorTask;
            if ($taskObj) {

                # Empty the Locator's move list IVs and update its task window
                $taskObj->resetMoveList();
            }
        }

        return 1;
    }

    sub addSpecialDepartureCallback {

        # Called by $self->enableRoomsColumn
        # When the character moves using an exit which doesn't send a room statement upon arrival
        #   in the new room - usually after some kind of faller - the pattern sent by the world to
        #   confirm arrival (such as 'You land in a big heap!') should be interpreted by the
        #   Locator task as a special kind of room statement
        # This callback adds a special departure pattern to the specified room and empties the
        #   Locator task's move list
        #
        # Expected arguments
        #   $roomObj    - The room to which the pattern should be added. When called by
        #                   $self->enableRoomsColumn, it will be the current room; when called by
        #                   ->enableRoomsPopupMenu, it will be the selected room
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the user
        #       doesn't supply a pattern
        #   1 otherwise

        my ($self, $roomObj, $check) = @_;

        # Local variables
        my ($pattern, $taskObj);

        # Check for improper arguments
        if (! defined $roomObj || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->addSpecialDepartureCallback',
                @_,
            );
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || (! $self->mapObj->currentRoom && ! $self->selectedRoom)
        ) {
            return undef;
        }

        # Prompt the user for a new special departure pattern to add to the room
        $pattern = $self->showEntryDialogue(
            'Add special departure to room',
            'Enter a pattern to match the special departure',
        );

        if (! $pattern) {

            return undef;

        } else {

            $self->worldModelObj->addExitPattern($roomObj, 'special', $pattern);

            # Import the Locator task
            $taskObj = $self->session->locatorTask;
            if ($taskObj) {

                # Empty the Locator's move list IVs and update its task window
                $taskObj->resetMoveList();
            }
        }

        return 1;
    }

    sub addUnspecifiedPatternCallback {

        # Called by $self->enableRoomsColumn
        # GA::Profile::World->unspecifiedRoomPatternList provides a list of patterns that match
        #   a line in 'unspecified' rooms (those that don't use a recognisable room statement;
        #   typically a room whose exit list is completely obscured)
        # Each room has its own list of patterns that match a line in 'unspecified' rooms; this
        #   callback adds a pattern to that list
        #
        # Expected arguments
        #   $roomObj    - The room to which the pattern should be added. When called by
        #                   $self->enableRoomsColumn, it will be the current room; when called by
        #                   ->enableRoomsPopupMenu, it will be the selected room
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the user
        #       doesn't supply a pattern
        #   1 otherwise

        my ($self, $roomObj, $check) = @_;

        # Local variables
        my ($pattern, $taskObj);

        # Check for improper arguments
        if (! defined $roomObj || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->addUnspecifiedPatternCallback',
                @_,
            );
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || (! $self->mapObj->currentRoom && ! $self->selectedRoom)
        ) {
            return undef;
        }

        # Prompt the user for a new unspecified room pattern to add to the room
        $pattern = $self->showEntryDialogue(
            'Add unspecified room pattern',
            'Enter a pattern to match an unspecified room',
        );

        if (! $pattern) {

            return undef;

        } else {

            $self->worldModelObj->addExitPattern($roomObj, 'unspecified', $pattern);

            # Import the Locator task
            $taskObj = $self->session->locatorTask;
            if ($taskObj) {

                # Empty the Locator's move list IVs and update its task window
                $taskObj->resetMoveList();
            }
        }

        return 1;
    }

    sub removeCheckedDirCallback {

        # Called by $self->enableRoomsColumn and ->enableRoomsPopupMenu
        # Removes one or all checked directions from the selected room
        #
        # Expected arguments
        #   $allFlag    - If set to TRUE, all checked directions are removed. If set to FALSE, the
        #                   user is prompted to choose an exit
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the user
        #       clicks 'cancel' on the 'dialogue' window
        #   1 otherwise

        my ($self, $allFlag, $check) = @_;

        # Local variables
        my (
            $choice,
            @comboList, @sortedList,
        );

        # Check for improper arguments
        if (! defined $allFlag || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->removeCheckedDirCallback',
                @_,
            );
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || ! $self->selectedRoom
            || ! $self->selectedRoom->checkedDirHash
        ) {
            return undef;
        }

        if ($allFlag) {

            # Delete all checked directions
            $self->selectedRoom->ivEmpty('checkedDirHash');

        } else {

            @comboList = sort {lc($a) cmp lc($b)} ($self->selectedRoom->ivKeys('checkedDirHash'));
            @sortedList = $self->session->currentDict->sortExits(@comboList);

            # Prompt the user for a checked direction to remove (even if there's only one)
            $choice = $self->showComboDialogue(
                'Remove checked direction',
                'Select the checked direction to remove',
                \@comboList,
            );

            if (! defined $choice) {

                return undef;

            } else {

                $self->selectedRoom->ivDelete('checkedDirHash', $choice);
            }
        }

        # Redraw the selected room in every window
        $self->worldModelObj->updateMaps('room', $self->selectedRoom);

        return 1;
    }

    sub setWildCallback {

        # Called by $self->enableRoomsColumn and ->enableRoomsPopupMenu
        # Sets the selected room(s)' wilderness mode
        #
        # Expected arguments
        #   $mode   - One of the values for GA::ModelObj::Room->wildMode - 'normal', 'border' or
        #               'wild'
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $mode, $check) = @_;

        # Local variables
        my @drawList;

        # Check for improper arguments
        if (
            ! defined $mode
            || ($mode ne 'normal' && $mode ne 'border' && $mode ne 'wild')
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->setWildCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap && (! $self->selectedRoom && ! $self->selectedRoomHash)) {

            return undef;
        }

        # For each selected room, convert their wilderness mode. The called function handles
        #   redrawing
        $self->worldModelObj->setWildernessRoom(
            $self->session,
            TRUE,                           # Update automapper windows
            $mode,
            $self->compileSelectedRooms(),
        );

        return 1;
    }

    sub selectExitCallback {

        # Called by $self->enableRoomsColumn
        # Prompts the user to select an exit manually
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails, if there are no
        #       exits to select, or if the user clicks 'cancel' in the 'dialogue' window
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $choice, $selectExitObj,

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

                $roomName = '(unnamed room)';
            } elsif (length($roomName) > 31) {
                $roomName = substr($roomName, 0, 29) . '...';
            }

            $msg .= $roomName . "'\n\n";

        } else {

            $msg = '';
        }

        if (@reducedList) {

            if (scalar @sortedList != scalar @reducedList) {

                $msg .= "Selected rooms (first " . $limit . " rooms of " . scalar @sortedList
                        . ")";

            } elsif (scalar @sortedList == 1) {

                $msg .= "Selected rooms (1 room)";

            } else {

                $msg .= "Selected rooms (" . scalar @sortedList . " rooms)";
            }

            foreach my $obj (@reducedList) {

                my $roomName;

                $msg .= "\n   #" . $obj->number . " '";

                $roomName = $obj->name;
                if ($roomName eq '<unnamed room>') {
                    $roomName = '(unnamed room)';
                } elsif (length($roomName) > 31) {
                    $roomName = substr($roomName, 0, 29) . '...';
                }

                $msg .= $roomName . "'";
            }
        }

        # Display a popup to show the results
        $self->showMsgDialogue(
            'Identify rooms',
            'info',
            $msg,
            'ok',
            undef,
            TRUE,           # Preserve newline characters in $msg
        );

        return 1;
    }

    sub updateVisitsCallback {

        # Called by $self->enableRoomsColumn, ->enableRoomsPopupMenu and ->drawMiscButtonSet
        # Adjusts the number of character visits shown in the selected room(s)
        # Normally, the current character's visits are changed. However, if $self->showChar is set,
        #   that character's visits are changed
        #
        # Expected arguments
        #   $mode   - 'increase' to increase the number of visits by one, 'decrease' to decrease the
        #               visits by one, 'manual' to let the user enter a value manually, 'reset' to
        #               reset the number to zero
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails, if the user clicks
        #       the 'cancel' button on a 'dialogue' window or for any other error
        #   1 otherwise

        my ($self, $mode, $check) = @_;

        # Local variables
        my (
            $char, $current, $result, $matchFlag,
            @roomList, @drawList,
        );

        # Check for improper arguments
        if (
            ! defined $mode
            || ($mode ne 'increase' && $mode ne 'decrease' && $mode ne 'manual' && $mode ne 'reset')
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->updateVisitsCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap || (! $self->selectedRoom && ! $self->selectedRoomHash)) {

            return undef;
        }

        # Get a list of selected room(s)
        @roomList = $self->compileSelectedRooms();

        # Decide which character to use
        if ($self->showChar) {

            $char = $self->showChar;

        } elsif ($self->session->currentChar) {

            $char = $self->session->currentChar->name;

        } else {

            $self->showMsgDialogue(
                'Update character visits',
                'error',
                'Can\'t update the number of visits - there is no current character set',
                'ok',
            );

            return undef;
        }

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN


            # ...and then show a confirmation (but only if the selected room is on a different
            #   level, or in a different region altogether)
            if (
                $self->selectedRoom->parent != $self->currentRegionmap->number
                || $self->selectedRoom->zPosBlocks != $self->currentRegionmap->currentLevel
            ) {
                $self->showMsgDialogue(
                    'Update character visits',
                    'info',
                    'Visits by \'' . $char . '\' to room #' . $self->selectedRoom->number
                    . ' set to ' . $current,
                    'ok',
                );
            }

        } else {

            # Check every selected room, stopping when we find one on the same level and in the
            #   same region
            OUTER: foreach my $roomObj (@roomList) {

                if (! defined $current) {

                    # (All the selected rooms now have the same number of visits, so $current only
                    #   needs to be set once)
                    $current = $roomObj->ivShow('visitHash', $char);
                    if (! $current) {

                        $current = 0;
                    }
                }

                if (
                    $roomObj->parent == $self->currentRegionmap->number
                    && $roomObj->zPosBlocks == $self->currentRegionmap->currentLevel
                ) {
                    $matchFlag = TRUE;
                    last OUTER;
                }
            }

            if (! $matchFlag) {

                # No selected rooms are actually visible, so show the confirmation
                $self->showMsgDialogue(
                    'Update character visits',
                    'info',
                    'Visits by \'' . $char . '\' in ' . (scalar @roomList) . ' rooms set to '
                    . $current,
                    'ok',
                );
            }
        }

        return 1;
    }

    sub toggleGraffitiCallback {

        # Called by $self->enableRoomsColumn, ->enableRoomsPopupMenu and ->drawMiscButtonSet
        # Toggles graffiti in the selected room(s)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (@roomList, @drawList);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->toggleGraffitiCallback', @_);
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || (! $self->selectedRoom && ! $self->selectedRoomHash)
            || ! $self->graffitiModeFlag
        ) {
            return undef;
        }

        # Get a list of selected room(s)
        @roomList = $self->compileSelectedRooms();
        foreach my $roomObj (@roomList) {

            push (@drawList, 'room', $roomObj);

            if (! $self->ivExists('graffitiHash', $roomObj->number)) {
                $self->ivAdd('graffitiHash', $roomObj->number, undef);
            } else {
                $self->ivDelete('graffitiHash', $roomObj->number);
            }
        }

        # Redraw the room(s) with graffiti on or off
        $self->markObjs(@drawList);
        $self->doDraw();
        # Update room counts in the window's title bar
        $self->setWinTitle();

        return 1;
    }

    sub setFilePathCallback {

        # Called by $self->enableRoomsColumn
        # Sets the file path for the world's source code file (if known) for the selected room
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        # Resets the list of exclusive profiles for the selected rooms
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $msg,
            @roomList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->resetExclusiveProfileCallback',
                @_,
            );
        }

        # Standard callback check
        if (! $self->currentRegionmap || (! $self->selectedRoom && ! $self->selectedRoomHash)) {

            return undef;
        }

        # Get a list of selected rooms
        @roomList = $self->compileSelectedRooms();

        # Reset their lists of exclusive profiles
        $self->worldModelObj->resetExclusiveProfiles(
            TRUE,           # Update Automapper windows now
            @roomList,
        );

        # Compose a message to display
        $msg = 'Reset exclusive profiles for ';

        if ($self->selectedRoom) {
            $msg .= '1 room';
        } else {
            $msg .= scalar @roomList . ' rooms';
        }

        $self->showMsgDialogue(
            'Reset exclusive profiles',
            'info',
            $msg,
            'ok',
        );

        return 1;
    }

    # Menu 'Exits' column callbacks

    sub changeDirCallback {

        # Called by $self->enableExitsColumn
        # Changes an existing exit's direction and/or its map direction, prompting the user for the
        #   new directions
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails, if the user clicks
        #       'cancel' on the 'dialogue' window or if the exit directions can't be changed
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($roomObj, $exitObj, $dir, $mapDir, $result);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->changeDirCallback', @_);
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || ! $self->selectedExit
            || (
                $self->selectedExit->drawMode ne 'primary'
                && $self->selectedExit->drawMode ne 'perm_alloc'
            )
        ) {
            return undef;
        }

        # When a user selects an exit, they may be referring either to the exit stored in
        #   $self->selectedExit, its twin exit (if there is one) or its shadow exit (if there is
        #   one). Prompt the user to find out which
        $exitObj = $self->promptSpecifyExit('Change direction for which exit?');
        if (! $exitObj) {

            # User clicked the 'cancel' button, or closed the 'dialogue' window
            return undef;
        }

        # Get the parent room object
        $roomObj = $self->worldModelObj->ivShow('modelHash', $exitObj->parent);

        # If this exit has been allocated a shadow exit, then the 'change direction' operation
        #   merely reassigns it as an unallocated exit
        if ($exitObj->shadowExit) {

            $result = $self->worldModelObj->changeShadowExitDir(
                $self->session,
                TRUE,       # Update Automapper windows now
                $roomObj,
                $exitObj,

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $exitObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->toggleExitTagCallback', @_);
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || (
                (! $self->selectedExit || ! $self->selectedExit->regionFlag)
                && ! $self->selectedExitTag
            )
        ) {
            return undef;
        }

        # Get the exit to use
        if ($self->selectedExit) {
            $exitObj = $self->selectedExit;
        } else {
            $exitObj = $self->selectedExitTag;
        }

        # Toggle the exit tag
        if (! $exitObj->exitTag) {

            $self->worldModelObj->applyExitTag(
                TRUE,           # Update Automapper windows now
                $exitObj,
            );

        } else {

            $self->worldModelObj->cancelExitTag(
                TRUE,           # Update Automapper windows now
                $exitObj,
            );
        }

        # For a selected exit tag - which has now been removed - select the exit instead
        $self->setSelectedObj(
            [$exitObj, 'exit'],
            FALSE,          # Select this object; unselect all other objects
        );

        return 1;
    }

    sub viewExitDestination {

        # Called by $self->enableExitTagsPopupMenu (only)
        # For a region exit, selects the destination room and changes the currently displayed region
        #   (and level) to show it
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $roomObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->viewExitDestination', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap || ! $self->selectedExitTag) {

            return undef;
        }

        # Get the exit's destination room
        $roomObj = $self->worldModelObj->ivShow('modelHash', $self->selectedExitTag->destRoom);

        # Select the destination room
        $self->setSelectedObj(
            [$roomObj, 'room'],
            FALSE,          # Select this object; unselect all other objects
        );

        # Centre the map over the selected room, changing the currently displayed region and level
        #   as necessary
        $self->centreMapOverRoom($roomObj);

        return 1;
    }

    sub editExitTagCallback {

        # Called by $self->enableLabelsColumn
        # Prompts the user to enter a new ->exitTag for the selected exit
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($exitObj, $text);

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        # Compile a list of exits which could be confused with the currently selected one
        ($stringListRef, $exitHashRef) = $self->compileExitList();
        if (! defined $stringListRef) {

            return undef;
        }

        @stringList = @$stringListRef;
        %exitHash = %$exitHashRef;

        # If there is more than one exit in the list, prompt the user to specify which one to delete
        if (scalar @stringList > 1) {

            # Compile the combo list
            if (@stringList == 2) {
                @comboList = ($bothString, @stringList);
            } elsif (@stringList > 2) {
                @comboList = ($allString, @stringList);
            } else {
                @comboList = @stringList;
            }

            # Prompt the user to choose which exit to delete
            $choice = $self->showComboDialogue(
                'Select exit',
                'Select which exit to delete',
                \@comboList,
            );

            if (! $choice) {

                return undef;

            } elsif ($choice eq $bothString || $choice eq $allString) {

                @finalList = values %exitHash;

            } else {

                push (@finalList, $exitHash{$choice});
            }

        } else {

            # There's only one exit on which to operate
            push (@finalList, $self->selectedExit);
        }

        # Delete the exit object(s) and instruct the world model to update its Automapper windows
        $self->worldModelObj->deleteExits(
            $self->session,
            TRUE,           # Update Automapper windows now
            @finalList,
        );

        return 1;
    }

    sub addBendCallback {

        # Called by $self->enableExitsPopupMenu (only)
        # After a right-click on an exit, when the user has selected 'add bend' in the popup menu,
        #   add a bend at the same position
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the bend is
        #       not added
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $startXPos, $startYPos, $clickXPos, $clickYPos, $stopXPos, $stopYPos, $resultType,
            $twinExitObj,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->addBendCallback', @_);
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || ! $self->selectedExit
            || (! $self->selectedExit->oneWayFlag && ! $self->selectedExit->twinExit)
            || $self->selectedExit->regionFlag
            || ! defined $self->exitClickXPosn
            || ! defined $self->exitClickYPosn
        ) {
            return undef;
        }

        # Get the absolute coordinates of the start of the middle (bending) section of the
        #   exit
        # At the same time, convert the absolute coordinates of the right-mouse click on the exit,
        #   and the absolute coordinates of the end of the bending section, into coordinates
        #   relative to the start of the bending section of the eixt
        ($startXPos, $startYPos, $clickXPos, $clickYPos, $stopXPos, $stopYPos, $resultType)
            = $self->findExitClick(
                $self->selectedExit,
                $self->exitClickXPosn,
                $self->exitClickYPosn,
            );

        # If the click wasn't in the parent room's gridblock, in the destination room's gridblock
        #   or too close to an existing bend...
        if (! $resultType) {

            # Add a bend to the exit
            $self->worldModelObj->addExitBend(
                FALSE,                          # Don't update Automapper windows yet
                $self->selectedExit,
                $startXPos, $startYPos,
                $clickXPos, $clickYPos,
                $stopXPos, $stopYPos,
            );

            # Repeat the process for the selected exit's twin (if there is one)
            if ($self->selectedExit->twinExit) {

                $twinExitObj = $self->worldModelObj->ivShow(
                    'exitModelHash',
                    $self->selectedExit->twinExit,
                );

                ($startXPos, $startYPos, $clickXPos, $clickYPos, $stopXPos, $stopYPos)
                    = $self->findExitClick(
                        $twinExitObj,
                        $self->exitClickXPosn,
                        $self->exitClickYPosn,
                    );

                $self->worldModelObj->addExitBend(
                    FALSE,                          # Don't update Automapper windows yet
                    $twinExitObj,
                    $startXPos, $startYPos,
                    $clickXPos, $clickYPos,
                    $stopXPos, $stopYPos,
                );
            }

            # Now we can redraw the exit
            $self->worldModelObj->updateMapExit(
                $self->selectedExit,
                $twinExitObj,               # May be 'undef'
            );

            return 1;

        } else {

            # If the click was too close to an existing bend, show a message explaining why nothing
            #   has happened (don't bother showing a message for other values of $resultType, which
            #   probably can't be returned to this function anyway)
            if ($resultType eq 'near_bend') {

                $self->showMsgDialogue(
                    'Add bend',
                    'error',
                    'Cannot add a bend - you clicked too close to an existing bend',
                    'ok',
                );
            }

            return undef;
        }
    }

    sub removeBendCallback {

        # Called by $self->enableExitsPopupMenu (only)
        # After a right-click on an exit, when the user has selected 'remove bend' in the popup
        #   menu, remove the bend closest to the clicked position
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails or if the mouse
        #       click was not near a bend
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($index, $twinExitObj);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->removeBendCallback', @_);
        }

        # Standard callback check
        if (
            ! $self->currentRegionmap
            || ! $self->selectedExit
            || (! $self->selectedExit->oneWayFlag && ! $self->selectedExit->twinExit)
            || ! $self->selectedExit->bendOffsetList
            || ! defined $self->exitClickXPosn
            || ! defined $self->exitClickYPosn
        ) {
            return undef;
        }

        # Find the number of the bend which is closest to the the clicked position
        $index = $self->findExitBend(
            $self->selectedExit,
            $self->exitClickXPosn,
            $self->exitClickYPosn,
        );

        if (! defined $index) {

            $self->showMsgDialogue(
                'Remove bend',
                'error',
                'Please right-click on the bend that you want to remove',
                'ok',
            );

            return undef;

        } else {

            # Remove this bend
            $self->worldModelObj->removeExitBend(
                $self->session,
                TRUE,                   # Update Automapper windows now
                $self->selectedExit,
                $index,                 # Remove this bend (first bend is numbered 0)
            );

            # If there is a twin exit, remove the corresponding bend at the same time
            if ($self->selectedExit->twinExit) {

                $twinExitObj = $self->worldModelObj->ivShow(
                    'exitModelHash',
                    $self->selectedExit->twinExit,
                );

                $self->worldModelObj->removeExitBend(
                    $self->session,
                    TRUE,               # Update Automapper windows now
                    $twinExitObj,
                    ((scalar $self->selectedExit->bendOffsetList / 2) - $index - 1),
                );
            }

            return 1;
        }
    }

    # Menu 'Labels' column callbacks

    sub addLabelAtBlockCallback {

        # Called by $self->enableLabelsColumn
        # Prompts the user to supply a gridblock (via a 'dialogue' window) and creates a label at
        #   that location
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments, if the standard callback check fails, if the user clicks
        #       the 'cancel' button on the 'dialogue' window or if the new label can't be created
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($xPosBlocks, $yPosBlocks, $zPosBlocks, $text, $style);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->addLabelAtBlockCallback',
                @_,
            );
        }

        # Standard callback check
        if (! $self->currentRegionmap) {

            return undef;
        }

        # Prompt the user for a gridblock
        ($xPosBlocks, $yPosBlocks, $zPosBlocks) = $self->promptGridBlock();
        if (! defined $xPosBlocks) {

            # User clicked the 'cancel' button
            return undef;
        }

        # Check that the specified gridblock actually exists
        if (
            ! $self->currentRegionmap->checkGridBlock(
                $xPosBlocks,
                $yPosBlocks,
                $zPosBlocks,
            )
        ) {
            $self->showMsgDialogue(
                'Add label at block',
                'error',
                'Invalid gridblock: x=' . $xPosBlocks . ', y=' . $yPosBlocks . ', z=' . $zPosBlocks,
                'ok',
            );
        }

        # Prompt the user to specify the label text
        ($text, $style) = $self->promptConfigLabel();
        # Free click mode must be reset (nothing special happens when the user clicks on the map)
        $self->reset_freeClickMode();

        if (defined $text && $text =~ m/\S/) {

            # Create a new label at the specified location
            $self->worldModelObj->addLabel(
                $self->session,
                TRUE,       # Update Automapper windows now
                $self->currentRegionmap,
                ($xPosBlocks * $self->currentRegionmap->blockWidthPixels),
                ($yPosBlocks * $self->currentRegionmap->blockHeightPixels),
                $zPosBlocks,
                $text,
                $style,
            );

            # The specified style is the preferred one
            if (defined $style) {

                $self->worldModelObj->set_mapLabelStyle($style);
            }

            return 1;

        } else {

            return undef;
        }
    }

    sub addLabelAtClickCallback {

        # Called by $self->enableCanvasPopupMenu and ->canvasEventHandler
        # Adds a label at a specified location on the current level
        #
        # Expected arguments
        #   $xPosPixels, $yPosPixels
        #       - The grid coordinates at which to create the label
        #
        # Return values
        #   'undef' on improper arguments, if the user clicks the 'cancel' button on the 'dialogue'
        #       window or if the new label can't be created
        #   1 otherwise

        my ($self, $xPosPixels, $yPosPixels, $check) = @_;

        # Local variables
        my ($text, $style);

        # Check for improper arguments
        if (! defined $xPosPixels || ! defined $yPosPixels || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->addLabelAtClickCallback',
                @_,
            );
        }

        # (No standard callback checks for this function)

        # Prompt the user to specify the label text
        ($text, $style) = $self->promptConfigLabel();
        # Free click mode must be reset (nothing special happens when the user clicks on the map)
        $self->reset_freeClickMode();

        if ($text || (defined $text && $text eq '0')) {        # '0' is a valid label

            # Create a new label at the specified location
            $self->worldModelObj->addLabel(
                $self->session,
                TRUE,       # Update Automapper windows now
                $self->currentRegionmap,
                $xPosPixels,
                $yPosPixels,
                $self->currentRegionmap->currentLevel,
                $text,
                $style,
            );

            # The specified style is the preferred one
            if (defined $style) {

                $self->worldModelObj->set_mapLabelStyle($style);
            }

            return 1;

        } else {

            return undef;
        }
    }

    sub setLabelCallback {

        # Called by $self->enableLabelsColumn
        # Prompts the user to modify a label's text and style (presenting only a list of map label
        #   style objects to choose from), using the same 'dialogue' window used to add a label
        #
        # Expected arguments
        #   $customiseFlag  - If FALSE, the 'dialogue' window only shows label text and style. If
        #                       TRUE, the 'dialogue' window shows all label IVs
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $customiseFlag, $check) = @_;

        # Local variables
        my ($text, $style);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->setLabelCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap || ! $self->selectedLabel) {

            return undef;
        }

        # Prompt the user to mod the label
        ($text, $style) = $self->promptConfigLabel($self->selectedLabel, $customiseFlag);

        if (defined $text && $text =~ m/\S/) {

            $self->worldModelObj->updateLabel(
                TRUE,                       # Update automapper windows now
                $self->session,
                $self->selectedLabel,
                $text,
                $style,
            );

            # The specified style is the preferred one
            if (defined $style) {

                $self->worldModelObj->set_mapLabelStyle($style);
            }

            return 1;

        } else {

            return undef;
        }
    }

    sub setLabelDirectCallback {

        # Called by $self->enableLabelsPopupMenu (only)
        #
        # Sets the selected label's label style, without needing to prompt the user any further
        #
        # Expected arguments
        #   $style      - The name of the label style to use
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $style, $check) = @_;

        # Check for improper arguments
        if (! defined $style || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->setLabelDirectCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap || ! $self->selectedLabel) {

            return undef;
        }

        $self->worldModelObj->updateLabel(
            TRUE,                           # Update automapper windows now
            $self->session,
            $self->selectedLabel,
            $self->selectedLabel->name,     # The label text remains unchanged
            $style,
        );

        # The specified style is the preferred one
        $self->worldModelObj->set_mapLabelStyle($style);

        return 1;
    }

    sub selectLabelCallback {

        # Called by $self->enableLabelsColumn
        # Prompts the user to select a label, from a combobox listing all the labels in the current
        #   regionmap
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the standard callback check fails
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $allString, $choice, $labelObj,
            @labelList, @sortedList, @comboList, @finalList,
            %comboHash,
        );

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->deleteLabelsCallback', @_);
        }

        # Standard callback check
        if (! $self->currentRegionmap || (! $self->selectedLabel && ! $self->selectedLabelHash)) {

            return undef;
        }

        # Prompt the user for confirmation before deleting multiple labels
        if ($self->selectedLabelHash) {

            $result = $self->showMsgDialogue(
                'Delete labels',
                'question',
                'Are you sure you want to delete ' . $self->ivPairs('selectedLabelHash')
                . ' labels?',
                'yes-no',
            );

            if ($result ne 'yes') {

                return undef;
            }
        }

        # Delete the selected label(s)
        return $self->worldModelObj->deleteLabels(
            TRUE,           # Update Automapper windows now
            $self->compileSelectedLabels(),
        );
    }

    # IV setting functions

    sub setMode {

        # Can be called by anything
        # Sets the automapper's operating mode and updates other IVs/widgets
        # NB If the Locator isn't running, a call to this function always sets the mode to 'wait'
        #
        # Expected arguments
        #   $mode   - The new mode:
        #               'wait'      - The automapper isn't doing anything
        #               'follow'    - The automapper is following the character's position, but not
        #                               updating the world model
        #               'update'    - The automapper is updating the world model as the character
        #                               moves around
        #
        # Return values
        #   'undef' on improper arguments, or if an attempt to switch to 'update' mode fails because
        #       the Locator task is expecting room descriptions
        #   1 otherwise

        my ($self, $mode, $check) = @_;

        # Local variables
        my (
            $taskObj, $title, $oldMode, $menuItemName, $radioMenuItem, $toolbarButtonName,
            $toolbarButton,
        );

        # Check for improper arguments
        if (
            ! defined $mode || ($mode ne 'wait' && $mode ne 'follow' && $mode ne 'update')
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->setMode', @_);
        }

        # Import the current session's Locator task
        $taskObj = $self->session->locatorTask;

        # If the Locator isn't running or if there is no current regionmap, the mode must be set to
        #   'wait'
        if (! $taskObj || ! $self->currentRegionmap) {

            $mode = 'wait';

        } elsif ($self->mode eq 'update' && $self->worldModelObj->disableUpdateModeFlag) {

            # This function is called just after GA::Obj::WorldModel->toggleDisableUpdateModeFlag
            #   has set ->disableUpdateModeFlag to TRUE. Now that update mode has been disabled,
            #   switch to 'follow' mode
            $mode = 'follow';

        } elsif (
            ($self->mode ne 'update' && $mode eq 'update')
            || ($self->mode eq 'wait' && $mode eq 'follow')
        ) {
            # Don't switch to update mode if it is disabled, or if the session is in 'connect
            #   offline' mode
            if (
                $mode eq 'update'
                && (
                    $self->worldModelObj->disableUpdateModeFlag
                    || $self->session->status eq 'offline'
                )
            ) {
                # Retain the current mode ('wait' or 'follow')
                $mode = $self->mode;

            # If we're trying to switch from 'wait' to 'follow' mode, or from 'wait/'follow' to
            #   'update' mode, the Locator task must not be expecting room descriptions (doing this
            #   prevents the map from adding rooms based on junk data, or from getting lost
            #   immediately because it expected the wrong room)
            # If the Locator is expecting descriptions, refuse to switch mode
            } elsif ($taskObj->moveList) {

                if ($taskObj->moveList == 1) {
                    $title = 'Set mode (1 missing room statement)';
                } else {
                    $title = 'Set mode (' . scalar $taskObj->moveList . ' missing room statements)';
                }

                $self->showMsgDialogue(
                    $title,
                    'warning',
                    'The automapper can\'t switch to \'' . $mode . '\' mode until the Locator task'
                    . ' is no longer expecting any rooms (try: Rooms - Locator task - Reset'
                    . ' Locator)',
                    'ok',
                );

                # Retain the current mode ('wait' or 'follow')
                $mode = $self->mode;
            }
        }

        # We need to compare the old/new settings of $self->mode in a moment
        $oldMode = $self->mode;
        # Set the automapper's new operating mode
        $self->ivPoke('mode', $mode);

        # Even if $self->mode hasn't changed, it might not match the menu items and the toolbar
        #   button; so we must make sure the right ones are activated. Use
        #   $self->ignoreMenuUpdateFlag so that toggling a menu item doesn't toggle a toolbar icon
        #   (and vice-versa)
        $self->ivPoke('ignoreMenuUpdateFlag', TRUE);

        # Update radio buttons in the menu (if the menu is visible)
        $menuItemName = 'set_'. $mode . '_mode';
        if ($self->menuBar && $self->ivExists('menuToolItemHash', $menuItemName)) {

            $radioMenuItem = $self->ivShow('menuToolItemHash', $menuItemName);
            $radioMenuItem->set_active(TRUE);
        }

        # Update toolbar buttons in the toolbar (if the toolbar is visible)
        $toolbarButtonName = 'icon_set_'. $mode . '_mode';
        if ($self->toolbarList && $self->ivExists('menuToolItemHash', $toolbarButtonName)) {

            $toolbarButton = $self->ivShow('menuToolItemHash', $toolbarButtonName);
            $toolbarButton->set_active(TRUE);
        }

        # Make sure that the radio/toolbar buttons for 'update mode' are sensitive, or not
        $self->restrictUpdateMode();
        $self->ivPoke('ignoreMenuUpdateFlag', FALSE);

        # In case the automapper object's ghost room gets set to the wrong room, switching to
        #   'wait' mode temporarily must reset it
        if ($self->mode eq 'wait' && $self->mapObj->ghostRoom) {

            $self->mapObj->setGhostRoom();      # Automatically redraws the room

        # If switching from 'wait' to 'follow'/'update' mode and there is a current room set, the
        #   ghost room will not be set, so st it
        } elsif (
            $oldMode eq 'wait'
            && $self->mode ne 'wait'
            && $self->mapObj->currentRoom
            && ! $self->mapObj->ghostRoom
        ) {
            $self->mapObj->setGhostRoom($self->mapObj->currentRoom);
        }

        if ($self->mapObj->currentRoom) {

            # Redraw the current room in its correct colour (default pink in 'wait' mode, default
            #   red in 'follow'/'update' mode)
            $self->markObjs('room', $self->mapObj->currentRoom);
            $self->doDraw();
        }

        return 1;
    }

    sub setCurrentRegion {

        # Called by $self->treeViewRowActivated and $self->newRegionCallback. Also called by
        #   GA::Obj::Map->setCurrentRoom
        # Sets the new current region and draws its map (if not already drawn)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   $name       - The name of the region (matches a key in
        #                   GA::Obj::WorldModel->regionmapHash). If set to 'undef', there is no
        #                   current region (and an empty map must be displayed)
        #   $forceFlag  - Set to TRUE when called by GA::Obj::Map->setCurrentRoom. Changes the
        #                   $name region's current level to show the current room, if there is one
        #                   (otherwise, the current level is only changed when the automapper is in
        #                   'follow' or 'update' mode)
        #
        # Return values
        #   'undef' on improper arguments or if a specified region $name doesn't match a known
        #       regionmap
        #   1 otherwise

        my ($self, $name, $forceFlag, $check) = @_;

        # Local variables
        my (
            $scrollXPos, $scrollYPos, $oldRegionmapObj, $oldParchmentObj, $count, $destroyFlag,
            $index, $regionmapObj, $destroyObj, $parchmentObj, $currentRoom,
            @newList,
            %occupyHash,

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

        my ($self, $mode, $check) = @_;

        # Local variables
        my $gdkWindow;

        # Check for improper arguments
        if (! defined $mode || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->set_freeClickMode', @_);
        }

        $self->ivPoke('freeClickMode', $mode);
        if ($self->bgColourMode eq 'rect_stop') {

            $self->ivPoke('bgColourMode', 'rect_start');
        }

        # Set the mouse icon accordingly
        $gdkWindow = $self->winWidget->get_window();
        if ($gdkWindow) {

            if ($mode eq 'add_room' || $mode eq 'add_label') {
                $gdkWindow->set_cursor($axmud::CLIENT->constMapAddCursor);
            } elsif ($mode eq 'connect_exit' || $mode eq 'move_room') {
                $gdkWindow->set_cursor($axmud::CLIENT->constMapConnectCursor);
            } elsif ($mode eq 'merge_room') {
                $gdkWindow->set_cursor($axmud::CLIENT->constMapMergeCursor);
            } else {
                $gdkWindow->set_cursor($axmud::CLIENT->constMapCursor);
            }
        }

        return 1;
    }

    sub reset_freeClickMode {

        my ($self, $check) = @_;

        # Local variables
        my $gdkWindow;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->reset_freeClickMode', @_);
        }

        $self->ivPoke('freeClickMode', 'default');

        # Reset the mouse icon
        $gdkWindow = $self->winWidget->get_window();
        if ($gdkWindow) {

            $gdkWindow->set_cursor($axmud::CLIENT->constMapCursor);
        }

        return 1;
    }

    sub set_ignoreMenuUpdateFlag {

        my ($self, $flag, $check) = @_;

        # Check for improper arguments
        if (! defined $flag || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->set_ignoreMenuUpdateFlag',
                @_,
            );
        }

        if ($flag) {
            $self->ivPoke('ignoreMenuUpdateFlag', TRUE);
        } else {
            $self->ivPoke('ignoreMenuUpdateFlag', FALSE);
        }

        return 1;
    }

    sub add_graffiti {

        my ($self, $roomObj, $check) = @_;

        # Check for improper arguments
        if (! defined $roomObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->add_graffiti', @_);
        }

        # Update IVs
        if ($self->graffitiModeFlag) {

            $self->ivAdd('graffitiHash', $roomObj->number);

            # Update room counts in the window's title bar
            $self->setWinTitle();
        }

        return 1;
    }

    sub del_graffiti {

        my ($self, @roomList) = @_;

        # (No improper arguments to check)

        if ($self->graffitiModeFlag) {

            foreach my $roomObj (@roomList) {

                $self->ivDelete('graffitiHash', $roomObj->number);
            }

            # Update room counts in the window's title bar
            $self->setWinTitle();
        }

        return 1;
    }

    sub set_mapObj {

        my ($self, $mapObj, $check) = @_;

        # Check for improper arguments
        if (! defined $mapObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->set_mapObj', @_);
        }

        # Update IVs
        $self->ivPoke('mapObj', $mapObj);

lib/Games/Axmud/Win/Map.pm  view on Meta::CPAN

    sub ctrlKeyFlag
        { $_[0]->{ctrlKeyFlag} }
    sub bgColourMode
        { $_[0]->{bgColourMode} }
    sub bgColourChoice
        { $_[0]->{bgColourChoice} }
    sub bgRectXPos
        { $_[0]->{bgRectXPos} }
    sub bgRectYPos
        { $_[0]->{bgRectYPos} }
    sub bgAllLevelFlag
        { $_[0]->{bgAllLevelFlag} }
    sub exitSensitivity
        { $_[0]->{exitSensitivity} }
    sub exitBendSize
        { $_[0]->{exitBendSize} }
    sub exitClickXPosn
        { $_[0]->{exitClickXPosn} }
    sub exitClickYPosn
        { $_[0]->{exitClickYPosn} }

    sub leftClickTime
        { $_[0]->{leftClickTime} }
    sub leftClickObj
        { $_[0]->{leftClickObj} }
    sub leftClickWaitTime
        { $_[0]->{leftClickWaitTime} }

    sub mode
        { $_[0]->{mode} }

    sub showChar
        { $_[0]->{showChar} }
    sub painterFlag
        { $_[0]->{painterFlag} }

    sub graffitiModeFlag
        { $_[0]->{graffitiModeFlag} }
    sub graffitiHash
        { my $self = shift; return %{$self->{graffitiHash}}; }

    sub constVectorHash
        { my $self = shift; return %{$self->{constVectorHash}}; }
    sub constDoubleVectorHash
        { my $self = shift; return %{$self->{constDoubleVectorHash}}; }
    sub constArrowVectorHash
        { my $self = shift; return %{$self->{constArrowVectorHash}}; }
    sub constPerpVectorHash
        { my $self = shift; return %{$self->{constPerpVectorHash}}; }
    sub constSpecialVectorHash
        { my $self = shift; return %{$self->{constSpecialVectorHash}}; }
    sub constTriangleCornerHash
        { my $self = shift; return %{$self->{constTriangleCornerHash}}; }
    sub constGtkAnchorHash
        { my $self = shift; return %{$self->{constGtkAnchorHash}}; }

    sub constMagnifyList
        { my $self = shift; return @{$self->{constMagnifyList}}; }
    sub constShortMagnifyList
        { my $self = shift; return @{$self->{constShortMagnifyList}}; }
    sub ignoreMenuUpdateFlag
        { $_[0]->{ignoreMenuUpdateFlag} }

    sub dragModeFlag
        { $_[0]->{dragModeFlag} }
    sub dragFlag
        { $_[0]->{dragFlag} }
    sub dragContinueFlag
        { $_[0]->{dragContinueFlag} }
    sub dragCanvasObj
        { $_[0]->{dragCanvasObj} }
    sub dragCanvasObjList
        { my $self = shift; return @{$self->{dragCanvasObjList}}; }
    sub dragModelObj
        { $_[0]->{dragModelObj} }
    sub dragModelObjType
        { $_[0]->{dragModelObjType} }
    sub dragInitXPos
        { $_[0]->{dragInitXPos} }
    sub dragInitYPos
        { $_[0]->{dragInitYPos} }
    sub dragCurrentXPos
        { $_[0]->{dragCurrentXPos} }
    sub dragCurrentYPos
        { $_[0]->{dragCurrentYPos} }
    sub dragFakeRoomList
        { my $self = shift; return @{$self->{dragFakeRoomList}}; }
    sub dragBendNum
        { $_[0]->{dragBendNum} }
    sub dragBendInitXPos
        { $_[0]->{dragBendInitXPos} }
    sub dragBendInitYPos
        { $_[0]->{dragBendInitYPos} }
    sub dragBendTwinNum
        { $_[0]->{dragBendTwinNum} }
    sub dragBendTwinInitXPos
        { $_[0]->{dragBendTwinInitXPos} }
    sub dragBendTwinInitYPos
        { $_[0]->{dragBendTwinInitYPos} }
    sub dragExitDrawMode
        { $_[0]->{dragExitDrawMode} }
    sub dragExitOrnamentsFlag
        { $_[0]->{dragExitOrnamentsFlag} }

    sub selectBoxFlag
        { $_[0]->{selectBoxFlag} }
    sub selectBoxCanvasObj
        { $_[0]->{selectBoxCanvasObj} }
    sub selectBoxInitXPos
        { $_[0]->{selectBoxInitXPos} }
    sub selectBoxInitYPos
        { $_[0]->{selectBoxInitYPos} }
    sub selectBoxCurrentXPos
        { $_[0]->{selectBoxCurrentXPos} }
    sub selectBoxCurrentYPos
        { $_[0]->{selectBoxCurrentYPos} }
}

# Package must return a true value
1



( run in 0.692 second using v1.01-cache-2.11-cpan-39bf76dae61 )