Games-Axmud

 view release on metacpan or  search on metacpan

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

# Copyright (C) 2011-2024 A S Lewis
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU
# General Public License as published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not,
# see <http://www.gnu.org/licenses/>.
#
#
# Games::Axmud::Strip::xxx
# Objects handling strips within an 'internal' window's client area

{ package Games::Axmud::Strip::MenuBar;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(Games::Axmud::Generic::Strip Games::Axmud);

    ##################
    # Constructors

    sub new {

        # Called by GA::Win::Internal->drawWidgets or ->addStripObj
        # Creates the GA::Strip::MenuBar - a non-compulsory strip object containing a Gtk3::MenuBar
        #
        # Expected arguments
        #   $number     - The strip object's number within the parent window (matches
        #                   GA::Win::Internal->stripCount, or -1 for a temporary strip object
        #                   created to access its default IVs)
        #   $winObj     - The parent window object (GA::Win::Internal). 'temp' for temporary strip
        #                   objects
        #
        # Optional arguments
        #   %initHash   - A hash containing arbitrary data to use as the strip object's
        #                   initialisation settings. The strip object should use default
        #                   initialisation settings unless it can succesfully interpret one or more
        #                   of the key-value pairs in the hash, if there are any
        #               - (This type of strip object requires no initialisation settings)
        #
        # Return values
        #   'undef' on improper arguments
        #   Blessed reference to the newly-created object on success

        my ($class, $number, $winObj, %initHash) = @_;

        # Local variables
        my %modHash;

        # Check for improper arguments
        if (! defined $class || ! defined $number || ! defined $winObj) {

            return $axmud::CLIENT->writeImproper($class . '->new', @_);
        }

        # Setup
        my $self = {
            _objName                    => 'strip_' . $number,
            _objClass                   => $class,
            _parentFile                 => undef,       # No parent file object
            _parentWorld                => undef,       # No parent file object
            _privFlag                   => TRUE,        # All IVs are private

            # Standard strip object IVs
            # -------------------------

            # The strip object's number within the parent window (matches
            #   GA::Win::Internal->stripCount, or -1 for a temporary strip object created to access
            #   its default IVs)
            number                      => $number,
            # The type of strip object (custom strip objects should use a ->type starting with
            #   'custom_' to avoid clashing with future built-in strip objects)
            type                        => 'menu_bar',
            # The parent window object (GA::Win::Internal). 'temp' for temporary strip objects
            winObj                      => $winObj,

            # Flag set to TRUE if the strip object is visible (has actually drawn widgets in the
            #   window), set to FALSE if it is not visible (has drawn no widgets in the window, but
            #   still exists in GA::Win::Internal->stripHash, etc)
            # The flag might be set to FALSE in strip objects like GA::Strip::GaugeBox, which might
            #   have gauges to draw, or not, depending on current conditions. (Most strip objects
            #   have this flag set to TRUE all the time)
            # If FALSE, GA::Win::Internal->drawWidgets and ->addStripObj don't draw any widgets when
            #   called by this object's functions
            # NB Strip objects are created with this flag set to TRUE or FALSE, but once created,
            #   the flag's value shouldn't be modified by anything other than
            #   GA::Win::Internal->hideStripObj and ->revealStripObj (which in turn call
            #   $self->set_visibleFlag)
            visibleFlag                 => TRUE,
            # Flag set to TRUE is the strip object should be given its share of any extra space
            #   within the packing box (the extra space is divided equally between all children of
            #   the box whose ->expandFlag is TRUE)
            expandFlag                  => FALSE,
            # Flag set to TRUE if any space given to the strip object by the 'expand' option is
            #   actually allocated within the strip object, FALSE if it is used as padding outside
            #   it (on both sides)
            fillFlag                    => FALSE,
            # Flag set to TRUE if the strip object should be packed into its window with a small
            #   gap between strip objects to either side; FALSE if not (can be set to FALSE if the
            #   the strip object's widgets are drawn in a way, such that a gap is not necessary,
            #   for example in the toolbar strip object)
            spacingFlag                 => TRUE,
            # Flag set to TRUE if only one instance of this strip object should be added to the
            #   parent window, set to FALSE if any number of instances can be added
            jealousyFlag                => TRUE,
            # Flag set to TRUE if this strip object can be added when $axmud::BLIND_MODE_FLAG is
            #   TRUE, FALSE if it can't be added (because it's not useful for visually-impaired
            #   users)
            blindFlag                   => FALSE,
            # Flag set to TRUE if the main container widget, stored in $self->packingBox, should be
            #   allowed to accept the focus, FALSE if not. The restriction is applied during the
            #   call to GA::Win::Internal->drawWidgets and ->addStripObj. Even if FALSE, widgets in
            #   the container widget can be set to accept the focus (e.g. the Gtk3::Entry in
            #   GA::Strip::MenuBar)
            allowFocusFlag              => FALSE,

            # Initialisation settings stored as a hash (see the comments above)
            initHash                    => \%modHash,
            # Reference to a function to call when some widget is used. This IV is set only when
            #   required by this type of strip object. It can be set by a call to
            #   $self->set_func() or by some setting in $self->initHash, which is applied in the
            #   call to $self->objEnable(). To obtain a reference to an OOP method, you can use the
            #   generic object function Games::Axmud->getMethodRef()
            funcRef                     => undef,
            # A value passed to ->funcRef when it is called which identifies this strip object and
            #   its widget(s). Can be any value, including 'undef'. It can be set by a call to
            #   $self->set_id() or by some setting in $self->initHash, which is applied in the call
            #   to $self->objEnable()
            funcID                      => undef,

            # The container widget for this strip object (usually a Gtk3::HBox or Gtk3::VBox). This
            #   widget is the one added to the window's main Gtk3::HBox or Gtk3::VBox
            packingBox                  => undef,       # Gtk3::VBox

            # Other IVs
            # ---------

            # Widgets
            menuBar                     => undef,       # Gtk3::MenuBar

            # Menu items which will be sensitised or desensitised, depending on the context. Hash
            #   in the form:
            #       $menuItemHash{'item_name'} = Gtk3_widget
            #   ...where:
            #       'item_name' is a descriptive scalar, e.g. 'move_up_level'
            #       'Gtk3_widget' is the Gtk3 menu item
            menuItemHash                => {},

            # The menu column for 'plugins', which can be extended by any loaded plugins
            pluginMenu                  => undef,       # Gtk3::Menu
            # Hash of sub-menus in the 'plugins' menu column, one for each plugin that wants one, in
            #   the form
            #       $pluginHash{plugin_name} = menu_widget
            pluginHash                  => {},
            # Additional list of menu items added by plugins, which will be sensitised or
            #   desensitised, depending on the context. Hash in the form:
            #       $pluginMenuItemHash{'plugin_name'} = Gtk3_widget
            pluginMenuItemHash          => {},

            # Flag set to TRUE when the 'save all sessions' menu item is selected (desensitises
            #   some other menu items)
            saveAllSessionsFlag         => FALSE,
        };

        # Bless the object into existence
        bless $self, $class;

        return $self;
    }

    ##################
    # Methods

    # Standard strip object functions

    sub objEnable {

        # Called by GA::Win::Internal->drawWidgets or ->addStripObj
        # Sets up the strip object's widgets
        #
        # Expected arguments
        #   $winmapObj  - The winmap object (GA::Obj::Winmap) that specifies the layout of the
        #                   parent window
        #
        # Return values
        #   'undef' on improper arguments
        #   1 on success

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

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

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

        # Create a packing box
        my $vBox = Gtk3::VBox->new(FALSE, 0);
        $vBox->set_border_width(0);

        # Create a Gtk3::MenuBar
        my $menuBar = Gtk3::MenuBar->new();
        $vBox->pack_start($menuBar, TRUE, TRUE, 0);

        # 'World' column
        my $menuColumn_world = $self->drawWorldColumn();
        my $menuItem_world = Gtk3::MenuItem->new('_World');
        $menuItem_world->set_submenu($menuColumn_world);
        $menuBar->append($menuItem_world);

        # 'File' column
        my $menuColumn_file = $self->drawFileColumn();
        my $menuItem_file = Gtk3::MenuItem->new('_File');
        $menuItem_file->set_submenu($menuColumn_file);
        $menuBar->append($menuItem_file);

        # 'Edit' column
        my $menuColumn_edit = $self->drawEditColumn();
        my $menuItem_edit = Gtk3::MenuItem->new('_Edit');
        $menuItem_edit->set_submenu($menuColumn_edit);
        $menuBar->append($menuItem_edit);

        # 'Interfaces' column
        my $menuColumn_interfaces = $self->drawInterfacesColumn();
        my $menuItem_interfaces = Gtk3::MenuItem->new('_Interfaces');
        $menuItem_interfaces->set_submenu($menuColumn_interfaces);
        $menuBar->append($menuItem_interfaces);

        # 'Tasks' column
        my $menuColumn_tasks = $self->drawTasksColumn();
        my $menuItem_tasks = Gtk3::MenuItem->new('_Tasks');
        $menuItem_tasks->set_submenu($menuColumn_tasks);
        $menuBar->append($menuItem_tasks);

        # 'Display' column
        my $menuColumn_display = $self->drawDisplayColumn();
        my $menuItem_display = Gtk3::MenuItem->new('_Display');
        $menuItem_display->set_submenu($menuColumn_display);
        $menuBar->append($menuItem_display);

        # 'Commands' column
        my $menuColumn_commands = $self->drawCommandsColumn();
        my $menuItem_commands = Gtk3::MenuItem->new('_Commands');
        $menuItem_commands->set_submenu($menuColumn_commands);
        $menuBar->append($menuItem_commands);

        # 'Recordings' column
        my $menuColumn_recordings = $self->drawRecordingsColumn();
        my $menuItem_recordings = Gtk3::MenuItem->new('_Recordings');
        $menuItem_recordings->set_submenu($menuColumn_recordings);
        $menuBar->append($menuItem_recordings);

        # 'Axbasic' column
        my $menuColumn_basic = $self->drawAxbasicColumn();
        my $menuItem_basic = Gtk3::MenuItem->new('_' . $axmud::BASIC_NAME);
        $menuItem_basic->set_submenu($menuColumn_basic);
        $menuBar->append($menuItem_basic);

        # 'Plugins' column
        my $menuColumn_plugins = $self->drawPluginsColumn();
        my $menuItem_plugins = Gtk3::MenuItem->new('_Plugins');
        $menuItem_plugins->set_submenu($menuColumn_plugins);
        $menuBar->append($menuItem_plugins);

        # 'Help' column
        my $menuColumn_help = $self->drawHelpColumn();
        my $menuItem_help = Gtk3::MenuItem->new('_Help');
        $menuItem_help->set_submenu($menuColumn_help);
        $menuBar->append($menuItem_help);

        # Update IVs
        $self->ivPoke('packingBox', $vBox);
        $self->ivPoke('menuBar', $menuBar);
        $self->ivPoke('pluginMenu', $menuColumn_plugins);

        # If any plugins have registered their desire to create sub-menus, call those plugins and
        #   create the sub-menus now
        foreach my $pluginObj (sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivValues('pluginHash'))) {

            my ($funcRef, $subMenu);

            if (
                $pluginObj->enabledFlag
                && $axmud::CLIENT->ivExists('pluginMenuFuncHash', $pluginObj->name)
            ) {
                $funcRef = $axmud::CLIENT->ivShow('pluginMenuFuncHash', $pluginObj->name);
                $subMenu = $self->addPluginWidgets($pluginObj->name);

                if ($funcRef && $subMenu) {

                    &$funcRef($self, $subMenu);
                }
            }
        }

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

        return 1;
    }

    sub objDestroy {

        # Called by GA::Win::Internal->removeStripObj, just before the strip is removed from its
        #   parent window, and also by ->winDestroy and ->resetWinmap, to give this object a chance
        #   to do any necessary tidying up
        #
        # 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 . '->objDestroy', @_);
        }

        # (No tidying up required for this type of strip object)
        #   ...

        return 1;
    }

#   sub setWidgetsIfSession {}              # Inherited from GA::Generic::Strip

#   sub setWidgetsChangeSession {}          # Inherited from GA::Generic::Strip

    # ->signal_connects are stored in $self->drawWorldColumn, etc

    # Other functions

    sub drawWorldColumn {

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

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

        # Local variables
        my ($mode, $choice);

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_connect = Gtk3::ImageMenuItem->new('_Connect...');
        my $menuImg_connect = Gtk3::Image->new_from_stock('gtk-connect', 'menu');
        $menuItem_connect->set_image($menuImg_connect);
        $menuItem_connect->signal_connect('activate' => sub {

            my $winObj;

            if ($self->winObj->visibleSession) {

                $self->winObj->visibleSession->pseudoCmd('connect', $mode);

            } else {

                # Can't use ';connect' because the parent window has no visible session

                # Check that the Connections window isn't already open
                if ($axmud::CLIENT->connectWin) {

                    # Window already open; draw attention to the fact by 'present'ing it
                    $axmud::CLIENT->connectWin->restoreFocus();

                } else {

                    # Open the Connections window
                    $winObj = $self->winObj->quickFreeWin(
                        'Games::Axmud::OtherWin::Connect',
                        $self->winObj->visibleSession,
                    );

                    if ($winObj) {

                        # Only one Connections window can be open at a time
                        $axmud::CLIENT->set_connectWin($winObj);
                    }
                }
            }
        });
        $menuColumn_world->append($menuItem_connect);
        # (Desensitised only when the setup wizwin is open)
        $self->ivAdd('menuItemHash', 'connect', $menuItem_connect);

        my $menuItem_reconnect = Gtk3::ImageMenuItem->new('_Reconnect');
        my $menuImg_reconnect = Gtk3::Image->new_from_stock('gtk-connect', 'menu');
        $menuItem_reconnect->set_image($menuImg_reconnect);
        $menuItem_reconnect->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('reconnect', $mode);
        });
        $menuColumn_world->append($menuItem_reconnect);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'reconnect', $menuItem_reconnect);

        my $menuItem_reconnectOffline = Gtk3::MenuItem->new('Reconnect _offline');
        $menuItem_reconnectOffline->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('reconnect -o', $mode);
        });
        $menuColumn_world->append($menuItem_reconnectOffline);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'reconnect_offline', $menuItem_reconnectOffline);

        my $menuItem_xConnect = Gtk3::MenuItem->new('Reconnect (_no save)');
        $menuItem_xConnect->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('xconnect', $mode);
        });
        $menuColumn_world->append($menuItem_xConnect);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'xconnect', $menuItem_xConnect);

        my $menuItem_xConnectOffline = Gtk3::MenuItem->new('Reconnect offline (no _save)');
        $menuItem_xConnectOffline->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('xconnect -o', $mode);
        });
        $menuColumn_world->append($menuItem_xConnectOffline);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'xconnect_offline', $menuItem_xConnectOffline);

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

        my $menuItem_login = Gtk3::ImageMenuItem->new('Character _login');
        my $menuImg_login = Gtk3::Image->new_from_stock('gtk-network', 'menu');
        $menuItem_login->set_image($menuImg_login);
        $menuItem_login->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('login', $mode);
        });
        $menuColumn_world->append($menuItem_login);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'login', $menuItem_login);

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

        my $menuItem_quit = Gtk3::ImageMenuItem->new('Send _quit');
        my $menuImg_quit = Gtk3::Image->new_from_stock('gtk-disconnect', 'menu');
        $menuItem_quit->set_image($menuImg_quit);
        $menuItem_quit->signal_connect('activate' => sub {

            if (
                $self->promptUser(
                    'Confirm quit',
                    'Are you sure you want to send the \'quit\' command?',
                )
            ) {
                $self->winObj->visibleSession->pseudoCmd('quit', $mode);
            }
        });
        $menuColumn_world->append($menuItem_quit);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'quit', $menuItem_quit);

        my $menuItem_qquit = Gtk3::MenuItem->new('Send quit (no sa_ve)');
        $menuItem_qquit->signal_connect('activate' => sub {

            if (
                $self->promptUser(
                    'Confirm quit',
                    'Are you sure you want to send the \'quit\' command without saving?',
                )
            ) {
                $self->winObj->visibleSession->pseudoCmd('qquit', $mode);
            }
        });
        $menuColumn_world->append($menuItem_qquit);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'qquit', $menuItem_qquit);

        my $menuItem_quitAll = Gtk3::MenuItem->new('Send quit (_all sessions)');
        $menuItem_quitAll->signal_connect('activate' => sub {

            if (
                $self->promptUser(
                    'Confirm quit',
                    'Are you sure you want to send the \'quit\' command in all sessions?',
                )
            ) {
                $self->winObj->visibleSession->pseudoCmd('quitall', $mode);
            }
        });
        $menuColumn_world->append($menuItem_quitAll);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'quit_all', $menuItem_quitAll);

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

        my $menuItem_exit = Gtk3::ImageMenuItem->new('_Exit session');
        my $menuImg_exit = Gtk3::Image->new_from_stock('gtk-disconnect', 'menu');
        $menuItem_exit->set_image($menuImg_exit);
        $menuItem_exit->signal_connect('activate' => sub {

            if (
                $self->promptUser(
                    'Confirm exit',
                    'Are you sure you want to exit this session?',
                )
            ) {
                $self->winObj->visibleSession->pseudoCmd('exit', $mode);
            }
        });
        $menuColumn_world->append($menuItem_exit);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'exit', $menuItem_exit);

        my $menuItem_xxit = Gtk3::MenuItem->new('E_xit session (no save)');
        $menuItem_xxit->signal_connect('activate' => sub {

            if (
                $self->promptUser(
                    'Confirm exit',
                    'Are you sure you want to exit this session without saving?',
                )
            ) {
                $self->winObj->visibleSession->pseudoCmd('xxit', $mode);
            }
        });
        $menuColumn_world->append($menuItem_xxit);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'xxit', $menuItem_xxit);

        my $menuItem_exitAll = Gtk3::MenuItem->new('Ex_it all sessions');
        $menuItem_exitAll->signal_connect('activate' => sub {

            if (
                $self->promptUser(
                    'Confirm exit',
                    'Are you sure you want to exit every session?',
                )
            ) {
                $self->winObj->visibleSession->pseudoCmd('exitall', $mode);
            }
        });
        $menuColumn_world->append($menuItem_exitAll);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'exit_all', $menuItem_exitAll);

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

        my $menuItem_stopSession = Gtk3::ImageMenuItem->new('S_top session');
        my $menuImg_stopSession = Gtk3::Image->new_from_stock('gtk-close', 'menu');
        $menuItem_stopSession->set_image($menuImg_stopSession);
        $menuItem_stopSession->signal_connect('activate' => sub {

            if (
                $self->winObj->visibleSession->status eq 'connected'
                && $axmud::CLIENT->confirmCloseMenuFlag
            ) {
                $choice = $self->winObj->showMsgDialogue(
                    'Stop session',
                    'question',
                    'This session is connected to a world. Are you sure you want to stop it?',
                    'yes-no',
                );

                if ($choice eq 'yes') {

                    $self->winObj->visibleSession->pseudoCmd('stopsession', $mode);
                }

            } else {

                $self->winObj->visibleSession->pseudoCmd('stopsession', $mode);
            }
        });
        $menuColumn_world->append($menuItem_stopSession);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'stop_session', $menuItem_stopSession);

        my $menuItem_stopClient = Gtk3::ImageMenuItem->new('Sto_p client');
        my $menuImg_stopClient = Gtk3::Image->new_from_stock('gtk-quit', 'menu');
        $menuItem_stopClient->set_image($menuImg_stopClient);
        $menuItem_stopClient->signal_connect('activate' => sub {

            # If there are any connected sessions or unsaved files, prompt the user before executing
            #   the client command; otherwise go ahead and do the ';stopclient' operation
            if (
                $axmud::CLIENT->checkSessions()
                || $self->promptUser(
                    'Confirm stop client',
                    'Are you sure you want to close the ' . $axmud::SCRIPT . ' client?',
                )
            ) {
                if ($self->winObj->visibleSession) {

                    $self->winObj->visibleSession->pseudoCmd('stopclient', $mode);

                } else {

                    # Can't use ';stopclient' because there is no current session
                    $axmud::CLIENT->stop();
                }
            }
        });
        $menuColumn_world->append($menuItem_stopClient);
        # (Desensitised only when the setup wizwin is open)
        $self->ivAdd('menuItemHash', 'stop_client', $menuItem_stopClient);

        # Setup complete
        return $menuColumn_world;
    }

    sub drawFileColumn {

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

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

        # Local variables
        my ($mode, $forceSwitch, $allSessionSwitch);

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

        # A pair of radio buttons toggle the value of this string between '' and ' -f', used with
        #   the ';save' command
        $forceSwitch = '';
        # Another pair of buttons toggle the value of this string between '' and ' -a', used with
        #   the ';save' command
        $allSessionSwitch = '';

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

            return undef;
        }

        my $menuItem_loadAll = Gtk3::ImageMenuItem->new('_Load all');
        my $menuImg_loadAll = Gtk3::Image->new_from_stock('gtk-open', 'menu');
        $menuItem_loadAll->set_image($menuImg_loadAll);
        $menuItem_loadAll->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('load', $mode);
        });
        $menuColumn_file->append($menuItem_loadAll);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'load_all', $menuItem_loadAll);

            # 'Load files' submenu
            my $subMenu_loadFile = Gtk3::Menu->new();

            my $menuItem_loadFile_worldModel = Gtk3::MenuItem->new('_World model file');
            $menuItem_loadFile_worldModel->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -m', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_worldModel);

            my $menuItem_loadFile_tasks = Gtk3::MenuItem->new('_Tasks file');
            $menuItem_loadFile_tasks->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -t', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_tasks);

            my $menuItem_loadFile_scripts = Gtk3::MenuItem->new('_Scripts file');
            $menuItem_loadFile_scripts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -s', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_scripts);

            my $menuItem_loadFile_contacts = Gtk3::MenuItem->new('_Contacts file');
            $menuItem_loadFile_contacts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -n', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_contacts);

            my $menuItem_loadFile_dicts = Gtk3::MenuItem->new('_Dictionaries file');
            $menuItem_loadFile_dicts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -y', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_dicts);

            my $menuItem_loadFile_toolbar = Gtk3::MenuItem->new('Tool_bar file');
            $menuItem_loadFile_toolbar->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -b', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_toolbar);

            my $menuItem_loadFile_userComm = Gtk3::MenuItem->new('_User commands file');
            $menuItem_loadFile_userComm->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -u', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_userComm);

            my $menuItem_loadFile_zonemaps = Gtk3::MenuItem->new('_Zonemaps file');
            $menuItem_loadFile_zonemaps->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -z', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_zonemaps);

            my $menuItem_loadFile_winmaps = Gtk3::MenuItem->new('Win_maps file');
            $menuItem_loadFile_winmaps->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -p', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_winmaps);

            my $menuItem_loadFile_ttsObjs = Gtk3::MenuItem->new('T_ext-to-speech file');
            $menuItem_loadFile_ttsObjs->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('load -x', $mode);
            });
            $subMenu_loadFile->append($menuItem_loadFile_ttsObjs);

        my $menuItem_loadFile = Gtk3::MenuItem->new('L_oad files');
        $menuItem_loadFile->set_submenu($subMenu_loadFile);
        $menuColumn_file->append($menuItem_loadFile);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'load_file', $menuItem_loadFile);

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

        my $menuItem_saveAll = Gtk3::ImageMenuItem->new('_Save all');
        my $menuImg_saveAll = Gtk3::Image->new_from_stock('gtk-save', 'menu');
        $menuItem_saveAll->set_image($menuImg_saveAll);
        $menuItem_saveAll->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd(
                'save ' . $forceSwitch . $allSessionSwitch,
                $mode,
            );
        });
        $menuColumn_file->append($menuItem_saveAll);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'save_all', $menuItem_saveAll);

            # 'Save files' submenu
            my $subMenu_saveFile = Gtk3::Menu->new();

            my $menuItem_saveFile_config = Gtk3::MenuItem->new('_Config file');
            $menuItem_saveFile_config->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -i' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_config);

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

            my $menuItem_saveFile_prof = Gtk3::MenuItem->new('_Profile files');
            $menuItem_saveFile_prof->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -d' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_prof);

            my $menuItem_saveFile_currentProf = Gtk3::MenuItem->new('C_urrent profile files');
            $menuItem_saveFile_currentProf->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -c' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_currentProf);

            my $menuItem_saveFile_worldProf = Gtk3::MenuItem->new('_World definition...');
            $menuItem_saveFile_worldProf->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a world profile
                my (
                    $choice,
                    @worldList,
                );

                # Get an ordered list of all world profiles
                @worldList = $self->getWorldList();
                if (@worldList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Save file',
                        'Select a world profile to save',
                        \@worldList,
                    );

                    if ($choice) {

                        # Save the file
                        $self->winObj->visibleSession->pseudoCmd(
                            'save -o ' . $choice . $forceSwitch,
                            $mode,
                        );
                    }
                }
            });
            $subMenu_saveFile->append($menuItem_saveFile_worldProf);

            my $menuItem_saveFile_currentWorld = Gtk3::MenuItem->new('Cu_rrent world files');
            $menuItem_saveFile_currentWorld->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -w' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_currentWorld);

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

            my $menuItem_saveFile_worldModel = Gtk3::MenuItem->new('World _model file');
            $menuItem_saveFile_worldModel->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -m' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_worldModel);

            my $menuItem_saveFile_tasks = Gtk3::MenuItem->new('_Tasks file');
            $menuItem_saveFile_tasks->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -t' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_tasks);

            my $menuItem_saveFile_scripts = Gtk3::MenuItem->new('_Scripts file');
            $menuItem_saveFile_scripts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -s' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_scripts);

            my $menuItem_saveFile_contacts = Gtk3::MenuItem->new('C_ontacts file');
            $menuItem_saveFile_contacts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -n' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_contacts);

            my $menuItem_saveFile_dicts = Gtk3::MenuItem->new('_Dictionaries file');
            $menuItem_saveFile_dicts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -y' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_dicts);

            my $menuItem_saveFile_toolbar = Gtk3::MenuItem->new('Tool_bar file');
            $menuItem_saveFile_toolbar->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -b' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_toolbar);

            my $menuItem_saveFile_userComm = Gtk3::MenuItem->new('Us_er commands file');
            $menuItem_saveFile_userComm->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -u' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_userComm);

            my $menuItem_saveFile_zonemaps = Gtk3::MenuItem->new('_Zonemaps file');
            $menuItem_saveFile_zonemaps->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -z' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_zonemaps);

            my $menuItem_saveFile_winmaps = Gtk3::MenuItem->new('W_inmaps file');
            $menuItem_saveFile_winmaps->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -p' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_winmaps);

            my $menuItem_saveFile_ttsObjs = Gtk3::MenuItem->new('Te_xt-to-speech file');
            $menuItem_saveFile_ttsObjs->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('save -x' . $forceSwitch, $mode);
            });
            $subMenu_saveFile->append($menuItem_saveFile_ttsObjs);

        my $menuItem_saveFile = Gtk3::MenuItem->new('S_ave files');
        $menuItem_saveFile->set_submenu($subMenu_saveFile);
        $menuColumn_file->append($menuItem_saveFile);
        # (Requires a visible session whose status is 'connected' or 'offline' and
        #   $self->saveAllSessionsFlag set to FALSE)
        $self->ivAdd('menuItemHash', 'save_file', $menuItem_saveFile);

            # 'Save options' submenu
            my $subMenu_saveOptions = Gtk3::Menu->new();

            my $menuColumn_forced_radio1 = Gtk3::RadioMenuItem->new_with_mnemonic(
                undef,
                'Forced saves o_ff',
            );
            $menuColumn_forced_radio1->signal_connect('toggled' => sub {

                if ($menuColumn_forced_radio1->get_active()) {
                    $forceSwitch = '';
                } else {
                    $forceSwitch = ' -f';
                }
            });
            $subMenu_saveOptions->append($menuColumn_forced_radio1);

            my $menuColumn_forced_radio2 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $menuColumn_forced_radio1->get_group(),
                'Forced saves o_n',
            );
            $subMenu_saveOptions->append($menuColumn_forced_radio2);

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

            my $menuColumn_allSession_radio1 = Gtk3::RadioMenuItem->new_with_mnemonic(
                undef,
                'Save in _this session',
            );
            $menuColumn_allSession_radio1->signal_connect('toggled' => sub {

                if ($menuColumn_allSession_radio1->get_active()) {

                    $allSessionSwitch = '';
                    $self->ivPoke('saveAllSessionsFlag', FALSE);

                } else {

                    $allSessionSwitch = ' -a';
                    $self->ivPoke('saveAllSessionsFlag', TRUE);
                }

                # Sensitise/desensitise menu bar/toolbar items, depending on current conditions
                $axmud::CLIENT->desktopObj->restrictWidgets();
            });
            $subMenu_saveOptions->append($menuColumn_allSession_radio1);

            my $menuColumn_allSession_radio2 = Gtk3::RadioMenuItem->new_with_mnemonic(
                $menuColumn_allSession_radio1->get_group(),
                'Save in _all sessions',
            );
            $subMenu_saveOptions->append($menuColumn_allSession_radio2);

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

            my $menuItem_autoSaves_off = Gtk3::MenuItem->new('T_urn auto-saves off');
            $menuItem_autoSaves_off->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('autosave off', $mode);
            });
            $subMenu_saveOptions->append($menuItem_autoSaves_off);

            my $menuItem_autoSaves_on = Gtk3::MenuItem->new('Tu_rn auto-saves on');
            $menuItem_autoSaves_on->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('autosave on', $mode);
            });
            $subMenu_saveOptions->append($menuItem_autoSaves_on);

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

            my $menuItem_autoSaves_setTime = Gtk3::MenuItem->new('_Set auto-save time...');
            $menuItem_autoSaves_setTime->signal_connect('activate' => sub {

                my $number = $self->winObj->showEntryDialogue(
                    'Set auto-save time',
                    'Enter a time in seconds',
                );

                if ($number) {

                    # Set the auto-save time
                    $self->winObj->visibleSession->pseudoCmd('autosave ' . $number, $mode);
                }
            });
            $subMenu_saveOptions->append($menuItem_autoSaves_setTime);

        my $menuItem_saveOptions = Gtk3::MenuItem->new('Sa_ve options');
        $menuItem_saveOptions->set_submenu($subMenu_saveOptions);
        $menuColumn_file->append($menuItem_saveOptions);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'save_options', $menuItem_saveOptions);

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

        my $menuItem_importFile = Gtk3::MenuItem->new('I_mport files...');
        $menuItem_importFile->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('importfiles', $mode);
        });
        $menuColumn_file->append($menuItem_importFile);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'import_files', $menuItem_importFile);

#        my $menuItem_exportAllFile = Gtk3::MenuItem->new('_Export all files...');
#        $menuItem_exportAllFile->signal_connect('activate' => sub {
#
#            $self->winObj->visibleSession->pseudoCmd('exportfiles', $mode);
#        });
#        $menuColumn_file->append($menuItem_exportAllFile);
#        # (Requires a visible session whose status is 'connected' or 'offline')
#        $self->ivAdd('menuItemHash', 'export_all_files', $menuItem_exportAllFile);

            # 'Export files' submenu
            my $subMenu_exportFile = Gtk3::Menu->new();

            my $menuItem_exportFile_world = Gtk3::MenuItem->new('_World files...');
            $menuItem_exportFile_world->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a world profile
                my (
                    $choice,
                    @worldList,
                );

                # Get an ordered list of all world profiles
                @worldList = $self->getWorldList();
                if (@worldList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export file',
                        'Select a world profile to export',
                        \@worldList,
                    );

                    if ($choice) {

                        # Export the file
                        $self->winObj->visibleSession->pseudoCmd(
                            'exportfiles -w ' . $choice,
                            $mode,
                        );
                    }
                }
            });
            $subMenu_exportFile->append($menuItem_exportFile_world);

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

            my $menuItem_exportFile_model = Gtk3::MenuItem->new('World _model file...');
            $menuItem_exportFile_model->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a world profile
                my (
                    $choice,
                    @worldList,
                );

                # Get an ordered list of all world profiles
                @worldList = $self->getWorldList();
                if (@worldList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export file',
                        'Select a world model to export',
                        \@worldList,
                    );

                    if ($choice) {

                        # Export the file
                        $self->winObj->visibleSession->pseudoCmd(
                            'exportfiles -m ' . $choice,
                            $mode,
                        );
                    }
                }
            });
            $subMenu_exportFile->append($menuItem_exportFile_model);

            my $menuItem_exportFile_tasks = Gtk3::MenuItem->new('_Tasks file');
            $menuItem_exportFile_tasks->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -t', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_tasks);

            my $menuItem_exportFile_scripts = Gtk3::MenuItem->new('_Scripts file');
            $menuItem_exportFile_scripts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -s', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_scripts);

            my $menuItem_exportFile_contacts = Gtk3::MenuItem->new('_Contacts file');
            $menuItem_exportFile_contacts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -n', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_contacts);

            my $menuItem_exportFile_dicts = Gtk3::MenuItem->new('_Dictionaries file');
            $menuItem_exportFile_dicts->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -y', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_dicts);

            my $menuItem_exportFile_toolbar = Gtk3::MenuItem->new('Tool_bar file');
            $menuItem_exportFile_toolbar->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -b', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_toolbar);

            my $menuItem_exportFile_userComm = Gtk3::MenuItem->new('_User commands file');
            $menuItem_exportFile_userComm->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -u', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_userComm);

            my $menuItem_exportFile_zonemaps = Gtk3::MenuItem->new('_Zonemaps file');
            $menuItem_exportFile_zonemaps->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -z', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_zonemaps);

            my $menuItem_exportFile_winmaps = Gtk3::MenuItem->new('W_inmaps file');
            $menuItem_exportFile_winmaps->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -p', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_winmaps);

            my $menuItem_exportFile_ttsObjs = Gtk3::MenuItem->new('Te_xt-to-speech file');
            $menuItem_exportFile_ttsObjs->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('exportfiles -x', $mode);
            });
            $subMenu_exportFile->append($menuItem_exportFile_ttsObjs);

        my $menuItem_exportFile = Gtk3::MenuItem->new('E_xport files');
        $menuItem_exportFile->set_submenu($subMenu_exportFile);
        $menuColumn_file->append($menuItem_exportFile);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'export_file', $menuItem_exportFile);

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

        my $menuItem_importData = Gtk3::MenuItem->new('_Import data...');
        $menuItem_importData->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('importdata', $mode);
        });
        $menuColumn_file->append($menuItem_importData);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'import_data', $menuItem_importData);

            # 'Export data' submenu
            my $subMenu_exportData = Gtk3::Menu->new();

            my $menuItem_exportData_otherProf = Gtk3::MenuItem->new('_Non-world profiles...');
            $menuItem_exportData_otherProf->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a non-world profile
                my (
                    $choice,
                    @otherProfList,
                );

                # Get an ordered list of all non-world profiles
                @otherProfList = $self->getOtherProfList();
                if (@otherProfList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a non-world profile to export',
                        \@otherProfList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -d ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_otherProf);

            my $menuItem_exportData_singleCage = Gtk3::MenuItem->new('_Single cage...');
            $menuItem_exportData_singleCage->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a cage
                my (
                    $choice,
                    @cageList,
                );

                # Get an ordered list of cages
                @cageList
                    = sort {lc($a) cmp lc($b)} ($self->winObj->visibleSession->ivKeys('cageHash'));
                if (@cageList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a cage to export',
                        \@cageList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -t ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_singleCage);

            my $menuItem_exportData_profCages = Gtk3::MenuItem->new('_All cages in profile...');
            $menuItem_exportData_profCages->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a profile
                my (
                    $choice,
                    @profList,
                );

                # Get an ordered list of profiles
                @profList
                    = sort {lc($a) cmp lc($b)} ($self->winObj->visibleSession->ivKeys('profHash'));

                if (@profList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a profile whose cages should be exported',
                        \@profList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -p ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_profCages);

            my $menuItem_exportData_template = Gtk3::MenuItem->new('_Profile template...');
            $menuItem_exportData_template->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a profile template
                my (
                    $choice,
                    @templList,
                );

                # Get an ordered list of all templates
                @templList = sort {lc($a) cmp lc($b)}
                                ($self->winObj->visibleSession->ivKeys('templateHash'));

                if (@templList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a profile template to export',
                        \@templList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -s ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_template);

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

            my $menuItem_exportData_initialTask = Gtk3::MenuItem->new('(_Global) initial task...');
            $menuItem_exportData_initialTask->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a (global) initial task
                my (
                    $choice,
                    @taskList,
                );

                # Get an ordered list of all (global) initial tasks
                @taskList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('initTaskHash'));
                if (@taskList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a (global) initial task to export',
                        \@taskList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -i ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_initialTask);

            my $menuItem_exportData_customTask = Gtk3::MenuItem->new('_Custom task...');
            $menuItem_exportData_customTask->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a custom task
                my (
                    $choice,
                    @taskList,
                );

                # Get an ordered list of all custom task names
                @taskList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('customTaskHash'));
                if (@taskList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a custom task to export',
                        \@taskList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -c ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_customTask);

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

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

                # Display a 'dialogue' window, so the user can choose a dictionary
                my (
                    $choice,
                    @dictList,
                );

                # Get an ordered list of all dictionaries, with the current dictionary at the top of
                #   the list
                @dictList = $self->getDictList();
                if (@dictList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a dictionary to export',
                        \@dictList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -y ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_dict);

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

                # Display a 'dialogue' window, so the user can choose a zonemap
                my (
                    $choice,
                    @zonemapList,
                );

                @zonemapList = (sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('zonemapHash')));
                if (@zonemapList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a zonemap to export',
                        \@zonemapList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -z ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_zonemap);

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

                # Display a 'dialogue' window, so the user can choose a winmap
                my (
                    $choice,
                    @winmapList,
                );

                @winmapList = (sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('winmapHash')));
                if (@winmapList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a winmap to export',
                        \@winmapList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -p ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_winmap);

            my $menuItem_exportData_colScheme = Gtk3::MenuItem->new('C_olour scheme...');
            $menuItem_exportData_colScheme->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a colour scheme
                my (
                    $choice,
                    @schemeList,
                );

                @schemeList = (
                    sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('colourSchemeHash'))
                );

                if (@schemeList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a colour scheme to export',
                        \@schemeList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -o ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_colScheme);

            my $menuItem_exportData_ttsObj = Gtk3::MenuItem->new('_TTS object...');
            $menuItem_exportData_ttsObj->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose a TTS object
                my (
                    $choice,
                    @objList,
                );

                @objList = (sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('ttsObjHash')));
                if (@objList) {

                    # Display the dialogue
                    $choice = $self->winObj->showComboDialogue(
                        'Export data',
                        'Select a text-to-speech object to export',
                        \@objList,
                    );

                    if ($choice) {

                        # Export the data
                        $self->winObj->visibleSession->pseudoCmd('exportdata -x ' . $choice, $mode);
                    }
                }
            });
            $subMenu_exportData->append($menuItem_exportData_ttsObj);

        my $menuItem_exportData = Gtk3::MenuItem->new('Export _data');
        $menuItem_exportData->set_submenu($subMenu_exportData);
        $menuColumn_file->append($menuItem_exportData);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'export_data', $menuItem_exportData);

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

            # 'Backup data' submenu
            my $subMenu_backupRestore = Gtk3::Menu->new();

            my $menuItem_backupData = Gtk3::MenuItem->new('_Backup all data files');
            $menuItem_backupData->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('backupdata', $mode);
            });
            $subMenu_backupRestore->append($menuItem_backupData);

            my $menuItem_restoreData = Gtk3::MenuItem->new('_Restore from backup...');
            $menuItem_restoreData->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('restoredata', $mode);
            });
            $subMenu_backupRestore->append($menuItem_restoreData);

        my $menuItem_backupRestore = Gtk3::MenuItem->new('_Backup data');
        $menuItem_backupRestore->set_submenu($subMenu_backupRestore);
        $menuColumn_file->append($menuItem_backupRestore);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'backup_restore_data', $menuItem_backupRestore);

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

        my $menuItem_showFiles = Gtk3::ImageMenuItem->new('S_how file objects');
        my $menuImg_showFiles = Gtk3::Image->new_from_stock('gtk-dialog-info', 'menu');
        $menuItem_showFiles->set_image($menuImg_showFiles);
        $menuItem_showFiles->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('showfile', $mode);
        });
        $menuColumn_file->append($menuItem_showFiles);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'show_files', $menuItem_showFiles);

        my $menuItem_disableSaveWorld = Gtk3::ImageMenuItem->new('Disable _world save');
        my $menuImg_disableSaveWorld = Gtk3::Image->new_from_stock('gtk-dialog-warning', 'menu');
        $menuItem_disableSaveWorld->set_image($menuImg_disableSaveWorld);
        $menuItem_disableSaveWorld->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('disablesaveworld', $mode);
        });
        $menuColumn_file->append($menuItem_disableSaveWorld);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'disable_world_save', $menuItem_disableSaveWorld);

        my $menuItem_disableSaveLoad = Gtk3::ImageMenuItem->new('Disabl_e all saves/loads');
        my $menuImg_disableSaveLoad = Gtk3::Image->new_from_stock('gtk-dialog-warning', 'menu');
        $menuItem_disableSaveLoad->set_image($menuImg_disableSaveLoad);
        $menuItem_disableSaveLoad->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('disablesaveload', $mode);
        });
        $menuColumn_file->append($menuItem_disableSaveLoad);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'disable_save_load', $menuItem_disableSaveLoad);

        # Setup complete
        return $menuColumn_file;
    }

    sub drawEditColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_quickPrefs = Gtk3::ImageMenuItem->new(
            '_Quick preferences...',
        );
        my $menuImg_quickPrefs = Gtk3::Image->new_from_stock('gtk-preferences', 'menu');
        $menuItem_quickPrefs->set_image($menuImg_quickPrefs);
        $menuItem_quickPrefs->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('editquick', $mode);
        });
        $menuColumn_edit->append($menuItem_quickPrefs);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'edit_quick_prefs', $menuItem_quickPrefs);

        my $menuItem_clientPrefs = Gtk3::ImageMenuItem->new(
            $axmud::SCRIPT . ' _preferences...',
        );
        my $menuImg_clientPrefs = Gtk3::Image->new_from_stock('gtk-preferences', 'menu');
        $menuItem_clientPrefs->set_image($menuImg_clientPrefs);
        $menuItem_clientPrefs->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('editclient', $mode);
        });
        $menuColumn_edit->append($menuItem_clientPrefs);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'edit_client_prefs', $menuItem_clientPrefs);

        my $menuItem_sessionPrefs = Gtk3::ImageMenuItem->new('_Session preferences...');
        my $menuImg_sessionPrefs = Gtk3::Image->new_from_stock('gtk-preferences', 'menu');
        $menuItem_sessionPrefs->set_image($menuImg_sessionPrefs);
        $menuItem_sessionPrefs->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('editsession', $mode);
        });
        $menuColumn_edit->append($menuItem_sessionPrefs);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'edit_session_prefs', $menuItem_sessionPrefs);

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

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

            $self->winObj->visibleSession->pseudoCmd('editworld', $mode);
        });
        $menuColumn_edit->append($menuItem_editWorld);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'edit_current_world', $menuItem_editWorld);

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

            $self->winObj->visibleSession->pseudoCmd('editguild', $mode);
        });
        $menuColumn_edit->append($menuItem_editGuild);
        # Requires a current session whose status is 'connected' or 'offline' and whose
        #   ->currentGuild is defined
        $self->ivAdd('menuItemHash', 'edit_current_guild', $menuItem_editGuild);

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

            $self->winObj->visibleSession->pseudoCmd('editrace', $mode);
        });
        $menuColumn_edit->append($menuItem_editRace);
        # Requires a current session whose status is 'connected' or 'offline' and whose
        #   ->currentRace is defined
        $self->ivAdd('menuItemHash', 'edit_current_race', $menuItem_editRace);

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

            $self->winObj->visibleSession->pseudoCmd('editchar', $mode);
        });
        $menuColumn_edit->append($menuItem_editChar);
        # Requires a current session whose status is 'connected' or 'offline' and whose
        #   ->currentChar is defined
        $self->ivAdd('menuItemHash', 'edit_current_char', $menuItem_editChar);

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

        my $menuItem_locatorWiz = Gtk3::ImageMenuItem->new('Run Locator w_izard...');
        my $menuImg_locatorWiz = Gtk3::Image->new_from_stock('gtk-page-setup', 'menu');
        $menuItem_locatorWiz->set_image($menuImg_locatorWiz);
        $menuItem_locatorWiz->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('locatorwizard', $mode);
        });
        $menuColumn_edit->append($menuItem_locatorWiz);
        # (Requires a visible session whose status is 'connected' or 'offline'. A
        #   corresponding menu item also appears in $self->drawTasksColumn)
        $self->ivAdd('menuItemHash', 'run_locator_wiz', $menuItem_locatorWiz);

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

            $self->winObj->visibleSession->pseudoCmd('editmodel', $mode);
        });
        $menuColumn_edit->append($menuItem_editWorldModel);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'edit_world_model', $menuItem_editWorldModel);

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

            $self->winObj->visibleSession->pseudoCmd('editdictionary', $mode);
        });
        $menuColumn_edit->append($menuItem_editDict);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'edit_dictionary', $menuItem_editDict);

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

            # 'Simulate' submenu
            my $subMenu_simulate = Gtk3::Menu->new();

            my $menuItem_simWorld = Gtk3::MenuItem->new('Simulate _world...');
            $menuItem_simWorld->signal_connect('activate' => sub {

                # Open a simulate world 'dialogue' window. The text entered is used in a
                #   ';simulateworld' command
                $self->winObj->visibleSession->pseudoCmd('simulateworld', $mode);
            });
            $subMenu_simulate->append($menuItem_simWorld);

            my $menuItem_simPrompt = Gtk3::MenuItem->new('Simulate _prompt...');
            $menuItem_simPrompt->signal_connect('activate' => sub {

                # Open a simulate prompt 'dialogue' window. The text entered is used in a
                #   ';simulateprompt' command
                $self->winObj->visibleSession->pseudoCmd('simulateprompt', $mode);
            });
            $subMenu_simulate->append($menuItem_simPrompt);

            my $menuItem_simCmd = Gtk3::MenuItem->new('Simulate _command...');
            $menuItem_simCmd->signal_connect('activate' => sub {

                # Prompt the user for a world command
                my $cmd = $self->winObj->showEntryDialogue(
                    'Simulate world command',
                    'Enter a world command (not actually sent to the world)',
                );

                if ($cmd) {

                    $self->winObj->visibleSession->pseudoCmd(
                        'simulatecommand <' . $cmd . '>',
                        $mode,
                    );
                }
            });
            $subMenu_simulate->append($menuItem_simCmd);

            my $menuItem_simHook = Gtk3::MenuItem->new('Simulate _hook event...');
            $menuItem_simHook->signal_connect('activate' => sub {

                my (
                    $interfaceModel, $choice, $number, $cancelFlag, $cmd,
                    @eventList, @hookDataList,
                );

                # Get the hook interface model, and from there, a list of hook events
                $interfaceModel = $axmud::CLIENT->ivShow('interfaceModelHash', 'hook');
                @eventList = sort {$a cmp $b} ($interfaceModel->ivKeys('hookEventHash'));

                # Prompt the user for a hook event
                $choice = $self->winObj->showComboDialogue(
                    'Simulate hook event',
                    'Enter a hook event for ' . $axmud::SCRIPT . ' to simulate',
                    \@eventList,
                );

                if ($choice) {

                    # How many items of hook data are expected?
                    $number = $interfaceModel->ivShow('hookEventHash', $choice);
                    if ($number == 1) {

                        my $result = $self->winObj->showEntryDialogue(
                            'Simulate hook event',
                            'Enter hook data #1 for the hook \'' . $choice . '\'',
                        );

                        if (! $result) {

                            # Don't simulate the hook event
                            $cancelFlag = TRUE;

                        } else {

                            push (@hookDataList, $result);
                        }

                    } elsif ($number == 2) {

                        my @resultList = $self->winObj->showDoubleEntryDialogue(
                            'Simulate hook event',
                            'Enter hook data #1 for the hook \'' . $choice . '\'',
                            'Enter hook data #2',
                        );

                        # Both hook data items must contain at least one character
                        if (! @resultList || ! $resultList[0] || ! $resultList[1]) {

                            # Don't simulate the hook event
                            $cancelFlag = TRUE;

                        } else {

                            push (@hookDataList, @resultList);
                        }
                    }

                    if (! $cancelFlag) {

                        # Prepare the client command to execute...
                        $cmd = 'simulatehook ' . $choice;
                        foreach my $item (@hookDataList) {

                            $cmd .= ' <' . $item . '>';
                        }

                        # ...and execute it
                        $self->winObj->visibleSession->pseudoCmd($cmd, $mode);
                    }
                }
            });
            $subMenu_simulate->append($menuItem_simHook);

        my $menuItem_simulate = Gtk3::MenuItem->new('Sim_ulate');
        $menuItem_simulate->set_submenu($subMenu_simulate);
        $menuColumn_edit->append($menuItem_simulate);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'simulate', $menuItem_simulate);

        my $menuItem_patternTest = Gtk3::MenuItem->new('_Test patterns...');
        $menuItem_patternTest->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('testpattern', $mode);
        });
        $menuColumn_edit->append($menuItem_patternTest);
        # (Desensitised only when the setup wizwin is open)
        $self->ivAdd('menuItemHash', 'test_pattern', $menuItem_patternTest);

        # Setup complete
        return $menuColumn_edit;
    }

    sub drawInterfacesColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_activeInterfaces = Gtk3::MenuItem->new('Acti_ve interfaces...');
        $menuItem_activeInterfaces->signal_connect('activate' => sub {

            # Open a session preference window on the notebook's second page, so the user can see
            #   the list of active interfaces immediately
            $self->winObj->visibleSession->pseudoCmd('editactiveinterface', $mode);
        });
        $menuColumn_interfaces->append($menuItem_activeInterfaces);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'active_interfaces', $menuItem_activeInterfaces);

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

            # 'Triggers' submenu
            my $subMenu_showTriggers = Gtk3::Menu->new();

            my $menuItem_worldTriggers = Gtk3::MenuItem->new('_World triggers...');
            $menuItem_worldTriggers->signal_connect('activate' => sub {

                # Open the cage window on the notebook's second page, so the user can see the list
                #   of triggers immediately
                $self->winObj->visibleSession->pseudoCmd('editcage -t', $mode);
            });
            $subMenu_showTriggers->append($menuItem_worldTriggers);

            my $menuItem_guildTriggers = Gtk3::MenuItem->new('_Guild triggers...');
            $menuItem_guildTriggers->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage trigger_guild_' . $self->winObj->visibleSession->currentGuild->name,
                    $mode,
                );
            });
            $subMenu_showTriggers->append($menuItem_guildTriggers);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   guild)
            $self->ivAdd('menuItemHash', 'guild_triggers', $menuItem_guildTriggers);

            my $menuItem_raceTriggers = Gtk3::MenuItem->new('_Race triggers...');
            $menuItem_raceTriggers->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage trigger_race_' . $self->winObj->visibleSession->currentRace->name,
                    $mode,
                );
            });
            $subMenu_showTriggers->append($menuItem_raceTriggers);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   race)
            $self->ivAdd('menuItemHash', 'race_triggers', $menuItem_raceTriggers);

            my $menuItem_charTriggers = Gtk3::MenuItem->new('_Character triggers...');
            $menuItem_charTriggers->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage trigger_char_' . $self->winObj->visibleSession->currentChar->name,
                    $mode,
                );
            });
            $subMenu_showTriggers->append($menuItem_charTriggers);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   character)
            $self->ivAdd('menuItemHash', 'char_triggers', $menuItem_charTriggers);

        my $menuItem_showTriggers = Gtk3::MenuItem->new('_Triggers');
        $menuItem_showTriggers->set_submenu($subMenu_showTriggers);
        $menuColumn_interfaces->append($menuItem_showTriggers);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'show_triggers', $menuItem_showTriggers);

            # 'Aliases' submenu
            my $subMenu_showAliases = Gtk3::Menu->new();

            my $menuItem_worldAliases = Gtk3::MenuItem->new('World _aliases...');
            $menuItem_worldAliases->signal_connect('activate' => sub {

                # Open the cage window on the notebook's second page, so the user can see the list
                #   of aliases immediately
                $self->winObj->visibleSession->pseudoCmd('editcage -a', $mode);
            });
            $subMenu_showAliases->append($menuItem_worldAliases);

            my $menuItem_guildAliases = Gtk3::MenuItem->new('_Guild aliases...');
            $menuItem_guildAliases->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage alias_guild_' . $self->winObj->visibleSession->currentGuild->name,
                    $mode,
                );
            });
            $subMenu_showAliases->append($menuItem_guildAliases);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   guild)
            $self->ivAdd('menuItemHash', 'guild_aliases', $menuItem_guildAliases);

            my $menuItem_raceAliases = Gtk3::MenuItem->new('_Race aliases...');
            $menuItem_raceAliases->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage alias_race_' . $self->winObj->visibleSession->currentRace->name,
                    $mode,
                );
            });
            $subMenu_showAliases->append($menuItem_raceAliases);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   race)
            $self->ivAdd('menuItemHash', 'race_aliases', $menuItem_raceAliases);

            my $menuItem_charAliases = Gtk3::MenuItem->new('_Character aliases...');
            $menuItem_charAliases->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage alias_char_' . $self->winObj->visibleSession->currentChar->name,
                    $mode,
                );
            });
            $subMenu_showAliases->append($menuItem_charAliases);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   character)
            $self->ivAdd('menuItemHash', 'char_aliases', $menuItem_charAliases);

        my $menuItem_showAliases = Gtk3::MenuItem->new('_Aliases');
        $menuItem_showAliases->set_submenu($subMenu_showAliases);
        $menuColumn_interfaces->append($menuItem_showAliases);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'show_aliases', $menuItem_showAliases);

            # 'Macros' submenu
            my $subMenu_showMacros = Gtk3::Menu->new();

            my $menuItem_worldMacros = Gtk3::MenuItem->new('_World macros...');
            $menuItem_worldMacros->signal_connect('activate' => sub {

                # Open the cage window on the notebook's second page, so the user can see the list
                #   of macros immediately
                $self->winObj->visibleSession->pseudoCmd('editcage -m', $mode);
            });
            $subMenu_showMacros->append($menuItem_worldMacros);

            my $menuItem_guildMacros = Gtk3::MenuItem->new('_Guild macros...');
            $menuItem_guildMacros->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage macro_guild_' . $self->winObj->visibleSession->currentGuild->name,
                    $mode,
                );
            });
            $subMenu_showMacros->append($menuItem_guildMacros);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   guild)
            $self->ivAdd('menuItemHash', 'guild_macros', $menuItem_guildMacros);

            my $menuItem_raceMacros = Gtk3::MenuItem->new('_Race macros...');
            $menuItem_raceMacros->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage macro_race_' . $self->winObj->visibleSession->currentRace->name,
                    $mode,
                );
            });
            $subMenu_showMacros->append($menuItem_raceMacros);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   race)
            $self->ivAdd('menuItemHash', 'race_macros', $menuItem_raceMacros);

            my $menuItem_charMacros = Gtk3::MenuItem->new('_Character macros...');
            $menuItem_charMacros->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage macro_char_' . $self->winObj->visibleSession->currentChar->name,
                    $mode,
                );
            });
            $subMenu_showMacros->append($menuItem_charMacros);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   character)
            $self->ivAdd('menuItemHash', 'char_macros', $menuItem_charMacros);

        my $menuItem_showMacros = Gtk3::MenuItem->new('_Macros');
        $menuItem_showMacros->set_submenu($subMenu_showMacros);
        $menuColumn_interfaces->append($menuItem_showMacros);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'show_macros', $menuItem_showMacros);

            # 'Timers' submenu
            my $subMenu_showTimers = Gtk3::Menu->new();

            my $menuItem_worldTimers = Gtk3::MenuItem->new('_World timers...');
            $menuItem_worldTimers->signal_connect('activate' => sub {

                # Open the cage window on the notebook's second page, so the user can see the list
                #   of timers immediately
                $self->winObj->visibleSession->pseudoCmd('editcage -i', $mode);
            });
            $subMenu_showTimers->append($menuItem_worldTimers);

            my $menuItem_guildTimers = Gtk3::MenuItem->new('_Guild timers...');
            $menuItem_guildTimers->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage timer_guild_' . $self->winObj->visibleSession->currentGuild->name,
                    $mode,
                );
            });
            $subMenu_showTimers->append($menuItem_guildTimers);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   guild)
            $self->ivAdd('menuItemHash', 'guild_timers', $menuItem_guildTimers);

            my $menuItem_raceTimers = Gtk3::MenuItem->new('_Race timers...');
            $menuItem_raceTimers->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage timer_race_' . $self->winObj->visibleSession->currentRace->name,
                    $mode,
                );
            });
            $subMenu_showTimers->append($menuItem_raceTimers);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   race)
            $self->ivAdd('menuItemHash', 'race_timers', $menuItem_raceTimers);

            my $menuItem_charTimers = Gtk3::MenuItem->new('_Character timers...');
            $menuItem_charTimers->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage timer_char_' . $self->winObj->visibleSession->currentChar->name,
                    $mode,
                );
            });
            $subMenu_showTimers->append($menuItem_charTimers);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   character)
            $self->ivAdd('menuItemHash', 'char_timers', $menuItem_charTimers);

        my $menuItem_showTimers = Gtk3::MenuItem->new('T_imers');
        $menuItem_showTimers->set_submenu($subMenu_showTimers);
        $menuColumn_interfaces->append($menuItem_showTimers);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'show_timers', $menuItem_showTimers);

            # 'Hooks' submenu
            my $subMenu_showHooks = Gtk3::Menu->new();

            my $menuItem_worldHooks = Gtk3::MenuItem->new('_World hooks...');
            $menuItem_worldHooks->signal_connect('activate' => sub {

                # Open the cage window on the notebook's second page, so the user can see the list
                #   of hooks immediately
                $self->winObj->visibleSession->pseudoCmd('editcage -h', $mode);
            });
            $subMenu_showHooks->append($menuItem_worldHooks);

            my $menuItem_guildHooks = Gtk3::MenuItem->new('_Guild hooks...');
            $menuItem_guildHooks->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage hook_guild_' . $self->winObj->visibleSession->currentGuild->name,
                    $mode,
                );
            });
            $subMenu_showHooks->append($menuItem_guildHooks);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   guild)
            $self->ivAdd('menuItemHash', 'guild_hooks', $menuItem_guildHooks);

            my $menuItem_raceHooks = Gtk3::MenuItem->new('_Race hooks...');
            $menuItem_raceHooks->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage hook_race_' . $self->winObj->visibleSession->currentRace->name,
                    $mode,
                );
            });
            $subMenu_showHooks->append($menuItem_raceHooks);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   race)
            $self->ivAdd('menuItemHash', 'race_hooks', $menuItem_raceHooks);

            my $menuItem_charHooks = Gtk3::MenuItem->new('_Character hooks...');
            $menuItem_charHooks->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage hook_char_' . $self->winObj->visibleSession->currentChar->name,
                    $mode,
                );
            });
            $subMenu_showHooks->append($menuItem_charHooks);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   character)
            $self->ivAdd('menuItemHash', 'char_hooks', $menuItem_charHooks);

        my $menuItem_showHooks = Gtk3::MenuItem->new('_Hooks');
        $menuItem_showHooks->set_submenu($subMenu_showHooks);
        $menuColumn_interfaces->append($menuItem_showHooks);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'show_hooks', $menuItem_showHooks);

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

            # 'Commands' submenu
            my $subMenu_showCmds = Gtk3::Menu->new();

            my $menuItem_worldCmds = Gtk3::MenuItem->new('_World commands...');
            $menuItem_worldCmds->signal_connect('activate' => sub {

                # Open the cage window on the notebook's second page, so the user can see the list
                #   OF commands immediately
                $self->winObj->visibleSession->pseudoCmd('editcage -c', $mode);
            });
            $subMenu_showCmds->append($menuItem_worldCmds);

            my $menuItem_guildCmds = Gtk3::MenuItem->new('_Guild commands...');
            $menuItem_guildCmds->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage cmd_guild_' . $self->winObj->visibleSession->currentGuild->name,
                    $mode,
                );
            });
            $subMenu_showCmds->append($menuItem_guildCmds);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   guild)
            $self->ivAdd('menuItemHash', 'guild_cmds', $menuItem_guildCmds);

            my $menuItem_raceCmds = Gtk3::MenuItem->new('_Race commands...');
            $menuItem_raceCmds->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage cmd_race_' . $self->winObj->visibleSession->currentRace->name,
                    $mode,
                );
            });
            $subMenu_showCmds->append($menuItem_raceCmds);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   race)
            $self->ivAdd('menuItemHash', 'race_cmds', $menuItem_raceCmds);

            my $menuItem_charCmds = Gtk3::MenuItem->new('_Character commands...');
            $menuItem_charCmds->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage cmd_char_' . $self->winObj->visibleSession->currentChar->name,
                    $mode,
                );
            });
            $subMenu_showCmds->append($menuItem_charCmds);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   character)
            $self->ivAdd('menuItemHash', 'char_cmds', $menuItem_charCmds);

        my $menuItem_showCmds = Gtk3::MenuItem->new('_Commands');
        $menuItem_showCmds->set_submenu($subMenu_showCmds);
        $menuColumn_interfaces->append($menuItem_showCmds);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'show_cmds', $menuItem_showCmds);

            # 'Routes' submenu
            my $subMenu_showRoutes = Gtk3::Menu->new();

            my $menuItem_worldRoutes = Gtk3::MenuItem->new('_World routes...');
            $menuItem_worldRoutes->signal_connect('activate' => sub {

                # Open the cage window on the notebook's second page, so the user can see the list
                #   OF routes immediately
                $self->winObj->visibleSession->pseudoCmd('editcage -r', $mode);
            });
            $subMenu_showRoutes->append($menuItem_worldRoutes);

            my $menuItem_guildRoutes = Gtk3::MenuItem->new('_Guild routes...');
            $menuItem_guildRoutes->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage route_guild_' . $self->winObj->visibleSession->currentGuild->name,
                    $mode,
                );
            });
            $subMenu_showRoutes->append($menuItem_guildRoutes);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   guild)
            $self->ivAdd('menuItemHash', 'guild_routes', $menuItem_guildRoutes);

            my $menuItem_raceRoutes = Gtk3::MenuItem->new('_Race routes...');
            $menuItem_raceRoutes->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage route_race_' . $self->winObj->visibleSession->currentRace->name,
                    $mode,
                );
            });
            $subMenu_showRoutes->append($menuItem_raceRoutes);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   race)
            $self->ivAdd('menuItemHash', 'race_routes', $menuItem_raceRoutes);

            my $menuItem_charRoutes = Gtk3::MenuItem->new('_Character routes...');
            $menuItem_charRoutes->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd(
                    'editcage route_char_' . $self->winObj->visibleSession->currentChar->name,
                    $mode,
                );
            });
            $subMenu_showRoutes->append($menuItem_charRoutes);
            # (Requires a visible session whose status is 'connected' or 'offline', and a current
            #   character)
            $self->ivAdd('menuItemHash', 'char_routes', $menuItem_charRoutes);

        my $menuItem_showRoutes = Gtk3::MenuItem->new('_Routes');
        $menuItem_showRoutes->set_submenu($subMenu_showRoutes);
        $menuColumn_interfaces->append($menuItem_showRoutes);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'show_routes', $menuItem_showRoutes);

        # Setup complete
        return $menuColumn_interfaces;
    }

    sub drawTasksColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_freezeTasks = Gtk3::CheckMenuItem->new('_Freeze all tasks');
        $menuItem_freezeTasks->signal_connect('toggled' => sub {

            $self->winObj->visibleSession->pseudoCmd('freezetask', $mode);
        });
        $menuColumn_tasks->append($menuItem_freezeTasks);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'freeze_tasks', $menuItem_freezeTasks);

            # 'Start new task' submenu
            my $subMenu_startNewTask = Gtk3::Menu->new();

            my @taskList = (
                'channels', 'chat', 'compass', 'condition', 'divert', 'inventory', 'locator',
                'status', 'watch',
            );

            my @mnemonicList = (
                '_Channels task', 'C_hat task', 'C_ompass task', 'Con_dition task', '_Divert task',
                '_Inventory task', '_Locator task', '_Status task', '_Watch task',
            );

            foreach my $task (@taskList) {

                my $menuItem_startNewTask_task = Gtk3::MenuItem->new(shift @mnemonicList);
                $menuItem_startNewTask_task->signal_connect('activate' => sub {

                    $self->winObj->visibleSession->pseudoCmd(
                        'starttask ' . $task,
                        $mode,
                    );
                });
                $subMenu_startNewTask->append($menuItem_startNewTask_task);
                # (Requires a visible session whose status is 'connected' or 'offline'; in addition,
                #   some of these tasks must be unique)
                $self->ivAdd('menuItemHash', $task . '_task_start', $menuItem_startNewTask_task);
            }

        my $menuItem_startNewTask = Gtk3::MenuItem->new('Start _new task');
        $menuItem_startNewTask->set_submenu($subMenu_startNewTask);
        $menuColumn_tasks->append($menuItem_startNewTask);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'start_new_task', $menuItem_startNewTask);

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

            # 'Channels task' submenu
            my $subMenu_channelsTask = Gtk3::Menu->new();

            my $menuItem_channelsTask_addPattern = Gtk3::MenuItem->new('Add _channel pattern...');
            $menuItem_channelsTask_addPattern->signal_connect('activate' => sub {

                my ($pattern, $channel);

                # Prompt the user for a pattern/channel
                ($pattern, $channel) = $self->winObj->showDoubleEntryDialogue(
                    'Add channel pattern',
                    'Enter a pattern (regex)',
                    'Enter a channel (1-16 chars)',
                );

                if (defined $pattern && defined $channel) {

                    $self->winObj->visibleSession->pseudoCmd(
                        'addchannelpattern <' . $channel . '> <' . $pattern . '>',
                        $mode,
                    );
                }
            });
            $subMenu_channelsTask->append($menuItem_channelsTask_addPattern);

            my $menuItem_channelsTask_addException = Gtk3::MenuItem->new(
                'Add _exception pattern...',
            );
            $menuItem_channelsTask_addException->signal_connect('activate' => sub {

                # Prompt the user for a pattern
                my $pattern = $self->winObj->showEntryDialogue(
                    'Add exception pattern',
                    'Enter a pattern (regex)',
                );

                if (defined $pattern) {

                    $self->winObj->visibleSession->pseudoCmd(
                        'addchannelpattern -e <' . $pattern . '>',
                        $mode,
                    );
                }
            });
            $subMenu_channelsTask->append($menuItem_channelsTask_addException);

            my $menuItem_channelsTask_listPattern = Gtk3::MenuItem->new('_List patterns');
            $menuItem_channelsTask_listPattern->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('listchannelpattern', $mode);
            });
            $subMenu_channelsTask->append($menuItem_channelsTask_listPattern);

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

            my $menuItem_channelsTask_emptyWindow = Gtk3::MenuItem->new('Empty Channels _window');
            $menuItem_channelsTask_emptyWindow->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('emptychannelswindow', $mode);
            });
            $subMenu_channelsTask->append($menuItem_channelsTask_emptyWindow);

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

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

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->channelsTask->prettyName . ' task',
                    $session->channelsTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_channelsTask->append($menuItem_channelsTask_editTask);

        my $menuItem_channelsTask = Gtk3::MenuItem->new('_Channels task');
        $menuItem_channelsTask->set_submenu($subMenu_channelsTask);
        $menuColumn_tasks->append($menuItem_channelsTask);
        # (Requires a visible session whose status is 'connected' or 'offline' and is running a
        #   Channels task)
        $self->ivAdd('menuItemHash', 'channels_task', $menuItem_channelsTask);

            # 'Chat task' submenu
            my $subMenu_chatTask = Gtk3::Menu->new();

            my $menuItem_chatTask_listen = Gtk3::MenuItem->new('_Listen for incoming calls');
            $menuItem_chatTask_listen->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('chatlisten', $mode);
            });
            $subMenu_chatTask->append($menuItem_chatTask_listen);

            my $menuItem_chatTask_ignore = Gtk3::MenuItem->new('_Ignore incoming calls');
            $menuItem_chatTask_ignore->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('chatignore', $mode);
            });
            $subMenu_chatTask->append($menuItem_chatTask_ignore);

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

            my $menuItem_chatTask_chatContact = Gtk3::MenuItem->new('_Chat with...');
            $menuItem_chatTask_chatContact->signal_connect('activate' => sub {

                my (
                    $choice,
                    @comboList,
                );

                # Get a sorted list of chat contact names
                @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('chatContactHash'));
                if (! @comboList) {

                    $self->winObj->showMsgDialogue(
                        'Chat with contact',
                        'error',
                        'There is no-one in your contacts list',
                        'ok',
                    );

                } else {

                    # Prompt the user to choose a chat contact
                    $choice = $self->winObj->showComboDialogue(
                        'Select chat contact',
                        'Select the chat contact to call',
                        \@comboList,
                    );

                    if ($choice) {

                        $self->session->pseudoCmd(
                            'chatcall ' . $choice,
                            $mode,
                        );
                    }
                }
            });
            $subMenu_chatTask->append($menuItem_chatTask_chatContact);

            my $menuItem_chatTask_chatMM = Gtk3::MenuItem->new('Chat using _MudMaster...');
            $menuItem_chatTask_chatMM->signal_connect('activate' => sub {

                my ($host, $port);

                # Prompt the user for a host and port
                ($host, $port) = $self->winObj->showDoubleEntryDialogue(
                    'Chat using MudMaster',
                    'Enter a DNS/IP address',
                    '(Optional) enter the port',
                );

                if ($host) {

                    if (! $port) {

                        # (Don't use an empty string as the port)
                        $self->winObj->visibleSession->pseudoCmd('chatmcall ' . $host, $mode);

                    } else {

                        $self->winObj->visibleSession->pseudoCmd(
                            'chatmcall ' . $host . ' ' . $port,
                            $mode,
                        );
                    }
                }
            });
            $subMenu_chatTask->append($menuItem_chatTask_chatMM);

            my $menuItem_chatTask_chatZChat = Gtk3::MenuItem->new('Chat using _zChat...');
            $menuItem_chatTask_chatZChat->signal_connect('activate' => sub {

                my ($host, $port);

                # Prompt the user for an address and port
                ($host, $port) = $self->winObj->showDoubleEntryDialogue(
                    'Chat using zChat',
                    'Enter a DNS/IP address',
                    '(Optional) enter the port',
                );

                if ($host) {

                    if (! $port) {

                        # (Don't use an empty string as the port)
                        $self->winObj->visibleSession->pseudoCmd('chatzcall ' . $host, $mode);

                    } else {

                        $self->winObj->visibleSession->pseudoCmd(
                            'chatzcall ' . $host . ' ' . $port,
                            $mode,
                        );
                    }
                }
            });
            $subMenu_chatTask->append($menuItem_chatTask_chatZChat);

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

            my $menuItem_chatTask_allowSnoop = Gtk3::MenuItem->new('_Allow everyone to snoop');
            $menuItem_chatTask_allowSnoop->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('chatset -a', $mode);
            });
            $subMenu_chatTask->append($menuItem_chatTask_allowSnoop);

            my $menuItem_chatTask_forbidSnoop = Gtk3::MenuItem->new('_Forbid all snooping');
            $menuItem_chatTask_forbidSnoop->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('chatset -f', $mode);
            });
            $subMenu_chatTask->append($menuItem_chatTask_forbidSnoop);

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

            my $menuItem_chatTask_hangUpAll = Gtk3::MenuItem->new('_Hang up on everyone');
            $menuItem_chatTask_hangUpAll->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('chathangup', $mode);
            });
            $subMenu_chatTask->append($menuItem_chatTask_hangUpAll);

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

            my $menuItem_chatTask_editTask = Gtk3::ImageMenuItem->new('Edit lead chat _task...');
            my $menuImg_chatTask_editTask = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
            $menuItem_chatTask_editTask->set_image($menuImg_chatTask_editTask);
            $menuItem_chatTask_editTask->signal_connect('activate' => sub {

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->chatTask->prettyName . ' task',
                    $session->chatTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_chatTask->append($menuItem_chatTask_editTask);
            # (Requires a visible session whose status is 'connected' or 'offline' and is running a
            #   Chat task)
            $self->ivAdd('menuItemHash', 'edit_chat_task', $menuItem_chatTask_editTask);

        my $menuItem_chatTask = Gtk3::MenuItem->new('C_hat task');
        $menuItem_chatTask->set_submenu($subMenu_chatTask);
        $menuColumn_tasks->append($menuItem_chatTask);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'chat_task', $menuItem_chatTask);

            # 'Compass' submenu
            my $subMenu_compassTask = Gtk3::Menu->new();

            my $menuItem_compassTask_enableKeypad
                = Gtk3::MenuItem->new('_Enable keypad world commands');
            $menuItem_compassTask_enableKeypad->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('compass on', $mode);
            });
            $subMenu_compassTask->append($menuItem_compassTask_enableKeypad);

            my $menuItem_compassTask_disableKeypad
                = Gtk3::MenuItem->new('_Disable keypad world commands');
            $menuItem_compassTask_disableKeypad->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('compass off', $mode);
            });
            $subMenu_compassTask->append($menuItem_compassTask_disableKeypad);

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

            my $menuItem_compassTask_addPattern = Gtk3::MenuItem->new('_Customise key...');
            $menuItem_compassTask_addPattern->signal_connect('activate' => sub {

                my (
                    $cmd, $keycode,
                    @comboList,
                );

                @comboList = (
                    'kp_0',
                    'kp_5',
                    'kp_divide',
                    'kp_multiply',
                    'kp_full_stop',
                    'kp_enter',
                );

                # Prompt the user for a pattern
                ($cmd, $keycode) = $self->winObj->showEntryComboDialogue(
                    'Customise keypad key',
                    '(Optional) world command',
                    'Keypad key',
                    \@comboList,
                    undef,          # No max chars
                    TRUE,           # Put combo above entry box
                );

                if (defined $keycode) {

                    if (defined $cmd) {

                        $self->winObj->visibleSession->pseudoCmd(
                            'compass ' . $keycode . ' ' . $cmd,
                            $mode,
                        );

                    } else {

                        $self->winObj->visibleSession->pseudoCmd('compass ' . $keycode, $mode);
                    }
                }
            });
            $subMenu_compassTask->append($menuItem_compassTask_addPattern);

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

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

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->compassTask->prettyName . ' task',
                    $session->compassTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_compassTask->append($menuItem_compassTask_editTask);

        my $menuItem_compassTask = Gtk3::MenuItem->new('C_ompass task');
        $menuItem_compassTask->set_submenu($subMenu_compassTask);
        $menuColumn_tasks->append($menuItem_compassTask);
        # (Requires a visible session whose status is 'connected' or 'offline' and is running a
        #   Compass task)
        $self->ivAdd('menuItemHash', 'compass_task', $menuItem_compassTask);

            # 'Divert task' submenu
            my $subMenu_divertTask = Gtk3::Menu->new();

            my $menuItem_divertTask_addPattern = Gtk3::MenuItem->new('Add _channel pattern...');
            $menuItem_divertTask_addPattern->signal_connect('activate' => sub {

                my ($pattern, $channel);

                # Prompt the user for a pattern/channel
                ($pattern, $channel) = $self->winObj->showDoubleEntryDialogue(
                    'Add channel pattern',
                    'Enter a pattern (regex)',
                    'Enter a channel (1-16 chars)',
                );

                if (defined $pattern && defined $channel) {

                    $self->winObj->visibleSession->pseudoCmd(
                        'addchannelpattern <' . $channel . '> <' . $pattern . '>',
                        $mode,
                    );
                }
            });
            $subMenu_divertTask->append($menuItem_divertTask_addPattern);

            my $menuItem_divertTask_addException = Gtk3::MenuItem->new('Add _exception pattern...');
            $menuItem_divertTask_addException->signal_connect('activate' => sub {

                # Prompt the user for a pattern
                my $pattern = $self->winObj->showEntryDialogue(
                    'Add exception pattern',
                    'Enter a pattern (regex)',
                );

                if (defined $pattern) {

                    $self->winObj->visibleSession->pseudoCmd(
                        'addchannelpattern -e <' . $pattern . '>',
                        $mode,
                    );
                }
            });
            $subMenu_divertTask->append($menuItem_divertTask_addException);

            my $menuItem_divertTask_listPattern = Gtk3::MenuItem->new('_List patterns');
            $menuItem_divertTask_listPattern->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('listchannelpattern', $mode);
            });
            $subMenu_divertTask->append($menuItem_divertTask_listPattern);

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

            my $menuItem_divertTask_emptyWindow = Gtk3::MenuItem->new('Empty divert _window');
            $menuItem_divertTask_emptyWindow->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('emptydivertwindow', $mode);
            });
            $subMenu_divertTask->append($menuItem_divertTask_emptyWindow);

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

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

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->divertTask->prettyName . ' task',
                    $session->divertTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_divertTask->append($menuItem_divertTask_editTask);

        my $menuItem_divertTask = Gtk3::MenuItem->new('_Divert task');
        $menuItem_divertTask->set_submenu($subMenu_divertTask);
        $menuColumn_tasks->append($menuItem_divertTask);
        # (Requires a visible session whose status is 'connected' or 'offline' and is running a
        #   Divert task)
        $self->ivAdd('menuItemHash', 'divert_task', $menuItem_divertTask);

            # 'Inventory/Condition task' submenu
            my $subMenu_inventoryTask = Gtk3::Menu->new();

            my $menuItem_inventoryTask_activateTask = Gtk3::MenuItem->new('_Activate task');
            $menuItem_inventoryTask_activateTask->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('activateinventory', $mode);
            });
            $subMenu_inventoryTask->append($menuItem_inventoryTask_activateTask);

            my $menuItem_inventoryTask_disactivateTask = Gtk3::MenuItem->new('_Disactivate task');
            $menuItem_inventoryTask_disactivateTask->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('disactivateinventory', $mode);
            });
            $subMenu_inventoryTask->append($menuItem_inventoryTask_disactivateTask);

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

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

                $self->winObj->visibleSession->pseudoCmd('sellall', $mode);
            });
            $subMenu_inventoryTask->append($menuItem_inventoryTask_sellAll);

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

                $self->winObj->visibleSession->pseudoCmd('dropall', $mode);
            });
            $subMenu_inventoryTask->append($menuItem_inventoryTask_dropAll);

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

            my $menuItem_inventoryTask_editTask = Gtk3::ImageMenuItem->new(
                'Edit current _Inventory task...',
            );
            my $menuImg_inventoryTask_editTask = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
            $menuItem_inventoryTask_editTask->set_image($menuImg_inventoryTask_editTask);
            $menuItem_inventoryTask_editTask->signal_connect('activate' => sub {

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->inventoryTask->prettyName . ' task',
                    $session->inventoryTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_inventoryTask->append($menuItem_inventoryTask_editTask);

            my $menuItem_conditionTask_editTask = Gtk3::ImageMenuItem->new(
                'Edit current _Condition task...',
            );
            my $menuImg_conditionTask_editTask = Gtk3::Image->new_from_stock('gtk-edit', 'menu');
            $menuItem_conditionTask_editTask->set_image($menuImg_conditionTask_editTask);
            $menuItem_conditionTask_editTask->signal_connect('activate' => sub {

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->conditionTask->prettyName . ' task',
                    $session->conditionTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_inventoryTask->append($menuItem_conditionTask_editTask);

        my $menuItem_inventoryTask = Gtk3::MenuItem->new('_Inventory/Condition tasks');
        $menuItem_inventoryTask->set_submenu($subMenu_inventoryTask);
        $menuColumn_tasks->append($menuItem_inventoryTask);
        # (Requires a visible session whose status is 'connected' or 'offline' and is running a
        #   Inventory task)
        $self->ivAdd('menuItemHash', 'inventory_task', $menuItem_inventoryTask);

            # 'Locator task' submenu
            my $subMenu_locatorTask = Gtk3::Menu->new();

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

                $self->winObj->visibleSession->pseudoCmd('resetlocatortask', $mode);
            });
            $subMenu_locatorTask->append($menuItem_locatorTask_resetTask);

            my $menuItem_locatorTask_toggleLocWin = Gtk3::MenuItem->new('Toggle task _window');
            $menuItem_locatorTask_toggleLocWin->signal_connect('activate' => sub {

                if ($self->winObj->visibleSession->locatorTask->winObj) {

                    $self->winObj->visibleSession->pseudoCmd('closetaskwindow locator', $mode);

                } else {

                    $self->winObj->visibleSession->pseudoCmd('opentaskwindow locator', $mode);
                }
            });
            $subMenu_locatorTask->append($menuItem_locatorTask_toggleLocWin);

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

            my $menuItem_locatorTask_toggleUnknown
                = Gtk3::MenuItem->new('T_oggle unknown word collection');
            $menuItem_locatorTask_toggleUnknown->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('collectunknownwords', $mode);
            });
            $subMenu_locatorTask->append($menuItem_locatorTask_toggleUnknown);

            my $menuItem_locatorTask_emptyUnknown
                = Gtk3::MenuItem->new('_Empty unknown word list');
            $menuItem_locatorTask_emptyUnknown->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('emptyunknownwords', $mode);
            });
            $subMenu_locatorTask->append($menuItem_locatorTask_emptyUnknown);

            my $menuItem_locatorTask_displayUnknown
                = Gtk3::MenuItem->new('_Display unknown word list');
            $menuItem_locatorTask_displayUnknown->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('listunknownwords', $mode);
            });
            $subMenu_locatorTask->append($menuItem_locatorTask_displayUnknown);

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

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

                $self->winObj->visibleSession->pseudoCmd('locatorwizard', $mode);
            });
            $subMenu_locatorTask->append($menuItem_locatorWiz);
            # (Requires a visible session whose status is 'connected' or 'offline'. A
            #   corresponding menu item also appears in $self->drawEditColumn)
            $self->ivAdd('menuItemHash', 'run_locator_wiz_2', $menuItem_locatorWiz);

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

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

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->locatorTask->prettyName . ' task',
                    $session->locatorTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_locatorTask->append($menuItem_locatorTask_editTask);

        my $menuItem_locatorTask = Gtk3::MenuItem->new('_Locator task');
        $menuItem_locatorTask->set_submenu($subMenu_locatorTask);
        $menuColumn_tasks->append($menuItem_locatorTask);
        # (Requires a visible session whose status is 'connected' or 'offline' and is running a
        #   Locator task)
        $self->ivAdd('menuItemHash', 'locator_task', $menuItem_locatorTask);

            # 'Status task' submenu
            my $subMenu_statusTask = Gtk3::Menu->new();

            my $menuItem_statusTask_activateTask = Gtk3::MenuItem->new('_Activate task');
            $menuItem_statusTask_activateTask->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('activatestatustask', $mode);
            });
            $subMenu_statusTask->append($menuItem_statusTask_activateTask);

            my $menuItem_statusTask_disactivateTask = Gtk3::MenuItem->new('_Disactivate task');
            $menuItem_statusTask_disactivateTask->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('disactivatestatustask', $mode);
            });
            $subMenu_statusTask->append($menuItem_statusTask_disactivateTask);

            my $menuItem_statusTask_resetCounters = Gtk3::MenuItem->new('_Reset all counters');
            $menuItem_statusTask_resetCounters->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('resetcounter', $mode);
            });
            $subMenu_statusTask->append($menuItem_statusTask_resetCounters);

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

            # 'Gauges' submenu
            my $subMenu_gauges = Gtk3::Menu->new();

            my $menuItem_statusTask_toggleStatWin = Gtk3::MenuItem->new('Toggle task _window');
            $menuItem_statusTask_toggleStatWin->signal_connect('activate' => sub {

                if ($self->winObj->visibleSession->statusTask->winObj) {

                    $self->winObj->visibleSession->pseudoCmd('closetaskwindow status', $mode);

                } else {

                    $self->winObj->visibleSession->pseudoCmd('opentaskwindow status', $mode);
                }
            });
            $subMenu_gauges->append($menuItem_statusTask_toggleStatWin);

            my $menuItem_statusTask_showGauge = Gtk3::MenuItem->new('Toggle _gauges');
            $menuItem_statusTask_showGauge->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('showstatusgauge', $mode);
            });
            $subMenu_gauges->append($menuItem_statusTask_showGauge);

            my $menuItem_statusTask_showGaugeLabel = Gtk3::MenuItem->new('Toggle gauge _labels');
            $menuItem_statusTask_showGaugeLabel->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('showstatusgauge -l', $mode);
            });
            $subMenu_gauges->append($menuItem_statusTask_showGaugeLabel);

            my $menuItem_gauges = Gtk3::MenuItem->new('D_isplay');
            $menuItem_gauges->set_submenu($subMenu_gauges);
            $subMenu_statusTask->append($menuItem_gauges);

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

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

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->statusTask->prettyName . ' task',
                    $session->statusTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_statusTask->append($menuItem_statusTask_editTask);

        my $menuItem_statusTask = Gtk3::MenuItem->new('_Status task');
        $menuItem_statusTask->set_submenu($subMenu_statusTask);
        $menuColumn_tasks->append($menuItem_statusTask);
        # (Requires a visible session whose status is 'connected' or 'offline' and is running a
        #   Status task)
        $self->ivAdd('menuItemHash', 'status_task', $menuItem_statusTask);

            # 'Watch' submenu
            my $subMenu_watchTask = Gtk3::Menu->new();

            my $menuItem_watchTask_emptyWindow = Gtk3::MenuItem->new('Empty watch _window');
            $menuItem_watchTask_emptyWindow->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('emptywatchwindow', $mode);
            });
            $subMenu_watchTask->append($menuItem_watchTask_emptyWindow);

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

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

                my $session = $self->winObj->visibleSession;

                # Open up a task 'edit' window to edit the task, with the 'main' window as the
                #   parent
                $self->winObj->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self->winObj,
                    $session,
                    'Edit ' . $session->watchTask->prettyName . ' task',
                    $session->watchTask,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => FALSE,           # Some IVs for current tasks not editable
                );
            });
            $subMenu_watchTask->append($menuItem_watchTask_editTask);

        my $menuItem_watchTask = Gtk3::MenuItem->new('_Watch task');
        $menuItem_watchTask->set_submenu($subMenu_watchTask);
        $menuColumn_tasks->append($menuItem_watchTask);
        # (Requires a visible session whose status is 'connected' or 'offline' and is running a
        #   Watch task)
        $self->ivAdd('menuItemHash', 'watch_task', $menuItem_watchTask);

        # Setup complete
        return $menuColumn_tasks;
    }

    sub drawDisplayColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_openAutomapper = Gtk3::ImageMenuItem->new('Open _automapper');
        my $menuImg_openAutomapper = Gtk3::Image->new_from_stock('gtk-jump-to', 'menu');
        $menuItem_openAutomapper->set_image($menuImg_openAutomapper);
        $menuItem_openAutomapper->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('openautomapper', $mode);
        });
        $menuColumn_display->append($menuItem_openAutomapper);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'open_automapper', $menuItem_openAutomapper);

        my $menuItem_openViewer = Gtk3::ImageMenuItem->new('Open object _viewer');
        my $menuImg_openViewer = Gtk3::Image->new_from_stock('gtk-jump-to', 'menu');
        $menuItem_openViewer->set_image($menuImg_openViewer);
        $menuItem_openViewer->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('opendataviewer', $mode);
        });
        $menuColumn_display->append($menuItem_openViewer);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'open_object_viewer', $menuItem_openViewer);

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

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

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

                $self->winObj->visibleSession->pseudoCmd('layerup', $mode);
            });
            $subMenu_currentLayer->append($menuItem_currentLayer_moveUp);

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

                $self->winObj->visibleSession->pseudoCmd('layerdown', $mode);
            });
            $subMenu_currentLayer->append($menuItem_currentLayer_moveDown);

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

            my $menuItem_currentLayer_moveTop = Gtk3::MenuItem->new('Move to _top');
            $menuItem_currentLayer_moveTop->signal_connect('activate' => sub {

                # Get the workspace grid object used by the visible session; can't rely on
                #   $self->workspaceGridObj because 'main' windows might appear on several
                #   workspaces
                my $gridObj
                    = $self->winObj->workspaceObj->findWorkspaceGrid($self->visibleSession);

                if ($gridObj) {

                    $self->winObj->visibleSession->pseudoCmd(
                        'setlayer ' . ($gridObj->maxLayers - 1),
                        $mode,
                    );
                }
            });
            $subMenu_currentLayer->append($menuItem_currentLayer_moveTop);

            my $menuItem_currentLayer_moveBottom = Gtk3::MenuItem->new('Move to _bottom');
            $menuItem_currentLayer_moveBottom->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('setlayer 0', $mode);
            });
            $subMenu_currentLayer->append($menuItem_currentLayer_moveBottom);

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

            my $menuItem_currentLayer_setLayer = Gtk3::MenuItem->new('_Set layer...');
            $menuItem_currentLayer_setLayer->signal_connect('activate' => sub {

                my ($gridObj, $result);

                $gridObj = $self->winObj->workspaceObj->findWorkspaceGrid($self->visibleSession);

                if ($gridObj) {

                    # Display a dialogue to choose the new layer
                    $result = $self->winObj->showEntryDialogue(
                        'Set layer',
                        'Enter the new layer (in the range 0-' . ($gridObj->maxLayers - 1) . ')',
                    );

                    if ($result) {

                        # Set the layer
                        $self->winObj->visibleSession->pseudoCmd('setlayer ' . $result, $mode);
                    }
                }
            });
            $subMenu_currentLayer->append($menuItem_currentLayer_setLayer);

        my $menuItem_currentLayer = Gtk3::MenuItem->new('Current _layer');
        $menuItem_currentLayer->set_submenu($subMenu_currentLayer);
        $menuColumn_display->append($menuItem_currentLayer);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'current_layer', $menuItem_currentLayer);

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

            my $menuItem_autoStore
                = Gtk3::CheckMenuItem->new('_Automatically store sizes/positions');
            $menuItem_autoStore->signal_connect('toggled' => sub {

                $self->winObj->visibleSession->pseudoCmd('togglewindowstorage', $mode);
            });
            $subMenu_windowStorage->append($menuItem_autoStore);

            my $menuItem_storeCurrent = Gtk3::MenuItem->new('_Store current sizes/positions');
            $menuItem_storeCurrent->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('applywindowstorage', $mode);
            });
            $subMenu_windowStorage->append($menuItem_storeCurrent);

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

            my $menuItem_resetStorage = Gtk3::MenuItem->new('_Clear stored sizes/positions');
            $menuItem_resetStorage->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('clearwindowstorage', $mode);
            });
            $subMenu_windowStorage->append($menuItem_resetStorage);

        my $menuItem_windowStorage = Gtk3::MenuItem->new('\'Grid\' _window storage');
        $menuItem_windowStorage->set_submenu($subMenu_windowStorage);
        $menuColumn_display->append($menuItem_windowStorage);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'window_storage', $menuItem_windowStorage);

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

        my $menuItem_testControls = Gtk3::MenuItem->new('_Test window controls');
        $menuItem_testControls->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('testwindowcontrols', $mode);
        });
        $menuColumn_display->append($menuItem_testControls);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'test_controls', $menuItem_testControls);

        my $menuItem_testPanels = Gtk3::MenuItem->new('T_est panels');
        $menuItem_testPanels->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('testpanel', $mode);
        });
        $menuColumn_display->append($menuItem_testPanels);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'test_panels', $menuItem_testPanels);

        # Setup complete
        return $menuColumn_display;
    }

    sub drawCommandsColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_repeat = Gtk3::ImageMenuItem->new('_Repeat...');
        my $menuImg_repeat = Gtk3::Image->new_from_stock('gtk-redo', 'menu');
        $menuItem_repeat->set_image($menuImg_repeat);
        $menuItem_repeat->signal_connect('activate' => sub {

            # Display a 'dialogue' window so the user can choose the command to repeat, and how
            #   often
            my ($cmd, $number);

            # Display the dialogue
            ($cmd, $number) = $self->winObj->showDoubleEntryDialogue(
                'Repeat command',
                'Enter a world command to repeat',
                'Enter how often to repeat it',
            );

            if ($cmd && $number) {

                # Issue the command
                $self->winObj->visibleSession->pseudoCmd('repeat ' . $number . ' ' . $cmd, $mode);
            }
        });
        $menuColumn_commands->append($menuItem_repeat);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'repeat_cmd', $menuItem_repeat);

        my $menuItem_repeatSecond = Gtk3::ImageMenuItem->new('Repeat each _second...');
        my $menuImg_repeatSecond = Gtk3::Image->new_from_stock('gtk-redo', 'menu');
        $menuItem_repeatSecond->set_image($menuImg_repeatSecond);
        $menuItem_repeatSecond->signal_connect('activate' => sub {

            # Display a 'dialogue' window so the user can choose the command to repeat, and how
            #   often
            my ($cmd, $number);

            # Display the dialogue
            ($cmd, $number) = $self->winObj->showDoubleEntryDialogue(
                'Repeat command',
                'Enter a world command to repeat once a second',
                'Enter how often to repeat it',
            );

            if ($cmd && $number) {

                # Issue the command
                $self->winObj->visibleSession->pseudoCmd(
                    'intervalrepeat ' . $number . ' 1 ' . $cmd,
                    $mode,
                );
            }
        });
        $menuColumn_commands->append($menuItem_repeatSecond);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'repeat_second', $menuItem_repeatSecond);

        my $menuItem_repeatInterval = Gtk3::ImageMenuItem->new('Repeat at _intervals...');
        my $menuImg_repeatInterval = Gtk3::Image->new_from_stock('gtk-redo', 'menu');
        $menuItem_repeatInterval->set_image($menuImg_repeatInterval);
        $menuItem_repeatInterval->signal_connect('activate' => sub {

            # Display a 'dialogue' window so the user can choose the command to repeat, and how
            #   often
            my ($cmd, $number, $interval);

            # Display the dialogue
            ($cmd, $number) = $self->winObj->showDoubleEntryDialogue(
                'Repeat command',
                'Enter a world command to repeat',
                'Enter how often to repeat it',
            );

            if ($cmd && $number) {

                # Display a second dialogue to get the interval
                $interval = $self->winObj->showEntryDialogue(
                    'Repeat command',
                    'Enter an interval (in seconds) between repetitions',
                );

                if ($interval) {

                    # Issue the command
                    $self->winObj->visibleSession->pseudoCmd(
                        'intervalrepeat ' . $number . ' ' . $interval . ' ' . $cmd,
                        $mode,
                    );
                }
            }
        });
        $menuColumn_commands->append($menuItem_repeatInterval);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'repeat_interval', $menuItem_repeatInterval);

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

        my $menuItem_cancelRepeat = Gtk3::ImageMenuItem->new('_Cancel repeating commands');
        my $menuImg_cancelRepeat = Gtk3::Image->new_from_stock('gtk-cancel', 'menu');
        $menuItem_cancelRepeat->set_image($menuImg_cancelRepeat);
        $menuItem_cancelRepeat->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('stopcommand', $mode);
        });
        $menuColumn_commands->append($menuItem_cancelRepeat);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'cancel_repeat', $menuItem_cancelRepeat);

        # Setup complete
        return $menuColumn_commands;
    }

    sub drawRecordingsColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_startStop = Gtk3::ImageMenuItem->new('_Start/stop recording');
        my $menuImg_startStop = Gtk3::Image->new_from_stock('gtk-media-record', 'menu');
        $menuItem_startStop->set_image($menuImg_startStop);
        $menuItem_startStop->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('record', $mode);
        });
        $menuColumn_recordings->append($menuItem_startStop);
        # (Requires a visible session whose status is 'connected' or 'offline', and
        #   GA::Session->recordingPausedFlag set to FALSE)
        $self->ivAdd('menuItemHash', 'start_stop_recording', $menuItem_startStop);

        my $menuItem_pauseResume = Gtk3::ImageMenuItem->new('_Pause/resume recording');
        my $menuImg_pauseResume = Gtk3::Image->new_from_stock('gtk-media-pause', 'menu');
        $menuItem_pauseResume->set_image($menuImg_pauseResume);
        $menuItem_pauseResume->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('pauserecording', $mode);
        });
        $menuColumn_recordings->append($menuItem_pauseResume);
        # (Requires a visible session whose status is 'connected' or 'offline', and
        #   GA::Session->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'pause_recording', $menuItem_pauseResume);

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

            # 'Add line' submenu
            my $subMenu_addLine = Gtk3::Menu->new();

            my $menuItem_addWorldCmd = Gtk3::MenuItem->new('Add _world command...');
            $menuItem_addWorldCmd->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose the command to add
                my $text = $self->winObj->showEntryDialogue(
                    'Recording',
                    'Enter a world command to add at this point in the recording',
                );

                if ($text) {

                    # Add the world command
                    $self->winObj->visibleSession->pseudoCmd(
                        'worldcommand <' . $text . '>',
                        $mode,
                    );
                }
            });
            $subMenu_addLine->append($menuItem_addWorldCmd);

            my $menuItem_addClientCmd = Gtk3::MenuItem->new('Add _client command...');
            $menuItem_addClientCmd->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose the command to add
                my $text = $self->winObj->showEntryDialogue(
                    'Recording',
                    'Enter a client command to add at this point in the recording',
                );

                if ($text) {

                    # Add the client command
                    $self->winObj->visibleSession->pseudoCmd(
                        'clientcommand <' . $text . '>',
                        $mode,
                    );
                }
            });
            $subMenu_addLine->append($menuItem_addClientCmd);

            my $menuItem_addComment = Gtk3::MenuItem->new('Add co_mment...');
            $menuItem_addComment->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose the comment to add
                my $text = $self->winObj->showEntryDialogue(
                    'Recording',
                    'Enter a comment to add at this point in the recording',
                );

                if ($text) {

                    # Add the comment
                    $self->winObj->visibleSession->pseudoCmd(
                        'comment <' . $text . '>',
                        $mode,
                    );
                }
            });
            $subMenu_addLine->append($menuItem_addComment);

        my $menuItem_addLine = Gtk3::MenuItem->new('Add _line');
        $menuItem_addLine->set_submenu($subMenu_addLine);
        $menuColumn_recordings->append($menuItem_addLine);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'recording_add_line', $menuItem_addLine);

            # 'Add break' submenu
            my $subMenu_addBreak = Gtk3::Menu->new();

            my $menuItem_ordinaryBreak = Gtk3::MenuItem->new('Add _ordinary break');
            $menuItem_ordinaryBreak->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('break', $mode);
            });
            $subMenu_addBreak->append($menuItem_ordinaryBreak);

            my $menuItem_triggerBreak = Gtk3::MenuItem->new('Add _trigger break...');
            $menuItem_triggerBreak->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose the trigger pattern to add
                my $text = $self->winObj->showEntryDialogue(
                    'Recording',
                    'Enter a trigger pattern to add at this point in the recording',
                );

                if ($text) {

                    # Add the trigger break
                    $self->winObj->visibleSession->pseudoCmd('break -t ' . $text, $mode);
                }
            });
            $subMenu_addBreak->append($menuItem_triggerBreak);

            my $menuItem_pauseBreak = Gtk3::MenuItem->new('Add _pause break...');
            $menuItem_pauseBreak->signal_connect('activate' => sub {

                # Display a 'dialogue' window, so the user can choose the pause interval to add
                my $text = $self->winObj->showEntryDialogue(
                    'Recording',
                    'Enter the length of the pause break to add at this point in the recording',
                );

                if ($text) {

                    # Add the pause break
                    $self->winObj->visibleSession->pseudoCmd('break -p ' . $text, $mode);
                }
            });
            $subMenu_addBreak->append($menuItem_pauseBreak);

            my $menuItem_locatorBreak = Gtk3::MenuItem->new('Add _Locator task break');
            $menuItem_locatorBreak->signal_connect('activate' => sub {

                $self->winObj->visibleSession->pseudoCmd('break -l', $mode);
            });
            $subMenu_addBreak->append($menuItem_locatorBreak);

        my $menuItem_addBreak = Gtk3::MenuItem->new('Add _break');
        $menuItem_addBreak->set_submenu($subMenu_addBreak);
        $menuColumn_recordings->append($menuItem_addBreak);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'recording_add_break', $menuItem_addBreak);

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

        my $menuItem_insertPoint = Gtk3::MenuItem->new('Set _insertion point...');
        $menuItem_insertPoint->signal_connect('activate' => sub {

            # Display a 'dialogue' window, so the user can choose the insertion point
            my $text = $self->winObj->showEntryDialogue(
                'Recording',
                'Enter the line number at which to continue the recording',
            );

            if ($text) {

                # Set the insertion point
                $self->winObj->visibleSession->pseudoCmd('insertrecording ' . $text, $mode);
            }
        });
        $menuColumn_recordings->append($menuItem_insertPoint);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'recording_set_insertion', $menuItem_insertPoint);

        my $menuItem_cancelInsert = Gtk3::MenuItem->new('_Cancel insertion point');
        $menuItem_cancelInsert->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('insertrecording', $mode);
        });
        $menuColumn_recordings->append($menuItem_cancelInsert);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'recording_cancel_insertion', $menuItem_cancelInsert);

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

        my $menuItem_deleteLine = Gtk3::MenuItem->new('_Delete line...');
        $menuItem_deleteLine->signal_connect('activate' => sub {

            # Display a 'dialogue' window, so the user can choose the line to delete
            my $text = $self->winObj->showEntryDialogue(
                'Recording',
                'Enter the number of the line to delete',
            );

            if ($text) {

                # Delete the line
                $self->winObj->visibleSession->pseudoCmd('deleterecording ' . $text, $mode);
            }
        });
        $menuColumn_recordings->append($menuItem_deleteLine);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'recording_delete_line', $menuItem_deleteLine);

        my $menuItem_deleteMulti = Gtk3::MenuItem->new('D_elete lines...');
        $menuItem_deleteMulti->signal_connect('activate' => sub {

            # Display a 'dialogue' window, so the user can choose the lines to delete
            my ($start, $stop) = $self->winObj->showDoubleEntryDialogue(
                'Recording',
                'Enter the number of the first line to be deleted',
                'Enter the number of the last line to be deleted',
            );

            if (defined $start && defined $stop) {

                # Delete the line
                $self->winObj->visibleSession->pseudoCmd(
                    'deleterecording ' . $start . ' ' . $stop,
                    $mode,
                );
            }
        });
        $menuColumn_recordings->append($menuItem_deleteMulti);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'recording_delete_multi', $menuItem_deleteMulti);

        my $menuItem_deleteLast = Gtk3::MenuItem->new('Delete l_ast line');
        $menuItem_deleteLast->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('deleterecording', $mode);

        });
        $menuColumn_recordings->append($menuItem_deleteLast);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE)
        $self->ivAdd('menuItemHash', 'recording_delete_last', $menuItem_deleteLast);

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

        my $menuItem_showRecording = Gtk3::MenuItem->new('S_how recording');
        $menuItem_showRecording->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('listrecording', $mode);
        });
        $menuColumn_recordings->append($menuItem_showRecording);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE or whose ->recordingList is not empty)
        $self->ivAdd('menuItemHash', 'show_recording', $menuItem_showRecording);

        my $menuItem_copyRecording = Gtk3::MenuItem->new('C_opy recording');
        $menuItem_copyRecording->signal_connect('activate' => sub {

            $self->winObj->visibleSession->pseudoCmd('copyrecording', $mode);
        });
        $menuColumn_recordings->append($menuItem_copyRecording);
        # (Requires a visible session whose status is 'connected' or 'offline' and a visible session
        #   whose ->recordingFlag set to TRUE or whose ->recordingList is not empty)
        $self->ivAdd('menuItemHash', 'copy_recording', $menuItem_copyRecording);

        # Setup complete
        return $menuColumn_recordings;
    }

    sub drawAxbasicColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_runScript = Gtk3::ImageMenuItem->new('_Run script...');
        my $menuImg_runScript = Gtk3::Image->new_from_stock('gtk-execute', 'menu');
        $menuItem_runScript->set_image($menuImg_runScript);
        $menuItem_runScript->signal_connect('activate' => sub {

            # Display a 'dialogue' window, so the user can choose the script to run
            my $text = $self->winObj->showEntryDialogue(
                'Run ' . $axmud::BASIC_NAME . ' script',
                'Enter the script to run (e.g. \'wumpus\')',
            );

            if ($text) {

                # Run the script
                $self->winObj->visibleSession->pseudoCmd('runscript ' . $text, $mode);
            }
        });
        $menuColumn_basic->append($menuItem_runScript);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'run_script', $menuItem_runScript);

        my $menuItem_runScriptTask = Gtk3::ImageMenuItem->new('Run script as _task...');
        my $menuImg_runScriptTask = Gtk3::Image->new_from_stock('gtk-execute', 'menu');
        $menuItem_runScriptTask->set_image($menuImg_runScriptTask);
        $menuItem_runScriptTask->signal_connect('activate' => sub {

            # Display a 'dialogue' window, so the user can choose the script to run
            my $text = $self->winObj->showEntryDialogue(
                'Run ' . $axmud::BASIC_NAME . ' script',
                'Enter the script to run as a task (e.g. \'wumpus\')',
            );

            if ($text) {

                # Run the script as a task
                $self->winObj->visibleSession->pseudoCmd('runscripttask ' . $text, $mode);
            }
        });
        $menuColumn_basic->append($menuItem_runScriptTask);
        # (Requires a visible session whose status is 'connected' or 'offline')
        $self->ivAdd('menuItemHash', 'run_script_task', $menuItem_runScriptTask);

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

        my $menuItem_checkScript = Gtk3::MenuItem->new('_Check script...');
        $menuItem_checkScript->signal_connect('activate' => sub {

            # Display a 'dialogue' window, so the user can choose the script to check
            my $text = $self->winObj->showEntryDialogue(
                'Check ' . $axmud::BASIC_NAME . ' script',
                'Enter the script to check (e.g. \'wumpus\')',
            );

            if ($text) {

                # Check the script
                $self->winObj->visibleSession->pseudoCmd('checkscript ' . $text, $mode);
            }
        });
        $menuColumn_basic->append($menuItem_checkScript);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'check_script', $menuItem_checkScript);

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

            # Display a 'dialogue' window, so the user can choose the script to check
            my $text = $self->winObj->showEntryDialogue(
                'Edit ' . $axmud::BASIC_NAME . ' script',
                'Enter the script to edit (e.g. \'wumpus\')',
            );

            if ($text) {

                # Edit the script
                $self->winObj->visibleSession->pseudoCmd('editscript ' . $text, $mode);
            }
        });
        $menuColumn_basic->append($menuItem_editScript);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'edit_script', $menuItem_editScript);

        # Setup complete
        return $menuColumn_basic;
    }

    sub drawPluginsColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_loadPlugin = Gtk3::ImageMenuItem->new('_Load plugin...');
        my $menuImg_loadPlugin = Gtk3::Image->new_from_stock('gtk-open', 'menu');
        $menuItem_loadPlugin->set_image($menuImg_loadPlugin);
        $menuItem_loadPlugin->signal_connect('activate' => sub {

            # Load the plugin
            $self->winObj->visibleSession->pseudoCmd('loadplugin', $mode);
        });
        $menuColumn_plugins->append($menuItem_loadPlugin);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'load_plugin', $menuItem_loadPlugin);

        my $menuItem_showPlugins = Gtk3::MenuItem->new('_Show plugins...');
        $menuItem_showPlugins->signal_connect('activate' => sub {

            # Open the client preference window at the right page
            my $prefWin = $self->winObj->createFreeWin(
                'Games::Axmud::PrefWin::Client',
                $self->winObj,
                $self->winObj->visibleSession,
                'Client preferences',
            );

            if ($prefWin) {

                $prefWin->notebook->set_current_page(2);
            }
        });
        $menuColumn_plugins->append($menuItem_showPlugins);
        # (Requires a visible session)
        $self->ivAdd('menuItemHash', 'show_plugin', $menuItem_showPlugins);

        # Setup complete
        return $menuColumn_plugins;
    }

    sub drawHelpColumn {

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

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

        # Local variables
        my $mode;

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

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

        # Import IVs (for convenience)
        $mode = $self->winObj->pseudoCmdMode;

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

            return undef;
        }

        my $menuItem_help = Gtk3::ImageMenuItem->new('_Help...');
        my $menuImg_help = Gtk3::Image->new_from_stock('gtk-help', 'menu');
        $menuItem_help->set_image($menuImg_help);
        $menuItem_help->signal_connect('activate' => sub {

            # Check that the About window isn't already open
            if ($axmud::CLIENT->aboutWin) {

                # Only one About window can be open at a time
                $axmud::CLIENT->aboutWin->restoreFocus();
                # Open it at the right page
                $axmud::CLIENT->aboutWin->notebook->set_current_page(2);

            } else {

                # Open the About window
                my $winObj = $self->winObj->quickFreeWin(
                    'Games::Axmud::OtherWin::About',
                    $self->winObj->visibleSession,
                    # Config
                    'first_tab' => 'help',
                );

                if ($winObj) {

                    $axmud::CLIENT->set_aboutWin($winObj);
                }
            }
        });
        $menuColumn_help->append($menuItem_help);
        # (Desensitised only when the setup wizwin is open)
        $self->ivAdd('menuItemHash', 'help', $menuItem_help);

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

        my $menuItem_about = Gtk3::ImageMenuItem->new('_About...');
        my $menuImg_about = Gtk3::Image->new_from_stock('gtk-about', 'menu');
        $menuItem_about->set_image($menuImg_about);
        $menuItem_about->signal_connect('activate' => sub {

            # Check that the About window isn't already open
            if ($axmud::CLIENT->aboutWin) {

                # Window already open; draw attention to the fact by 'present'ing it
                $axmud::CLIENT->aboutWin->restoreFocus();
                # Open it at the right page
                $axmud::CLIENT->aboutWin->notebook->set_current_page(0);

            } else {

                # Open the About window
                my $winObj = $self->winObj->quickFreeWin(
                    'Games::Axmud::OtherWin::About',
                    $self->winObj->visibleSession,
                    # Config
                    'first_tab' => 'about',
                );

                if ($winObj) {

                    # Only one About window can be open at a time
                    $axmud::CLIENT->set_aboutWin($winObj);
                }
            }
        });
        $menuColumn_help->append($menuItem_about);
        # (Desensitised only when the setup wizwin is open)
        $self->ivAdd('menuItemHash', 'about', $menuItem_about);

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

            # Check that the About window isn't already open
            if ($axmud::CLIENT->aboutWin) {

                # Window already open; draw attention to the fact by 'present'ing it
                $axmud::CLIENT->aboutWin->restoreFocus();
                # Open it at the right page
                $axmud::CLIENT->aboutWin->notebook->set_current_page(1);

            } else {

                # Open the About window
                my $winObj = $self->winObj->quickFreeWin(
                    'Games::Axmud::OtherWin::About',
                    $self->winObj->visibleSession,
                    # Config
                    'first_tab' => 'credits',
                );

                if ($winObj) {

                    # Only one About window can be open at a time
                    $axmud::CLIENT->set_aboutWin($winObj);
                }
            }
        });
        $menuColumn_help->append($menuItem_credits);
        # (Desensitised only when the setup wizwin is open)
        $self->ivAdd('menuItemHash', 'credits', $menuItem_credits);

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

            # Check that the About window isn't already open
            if ($axmud::CLIENT->aboutWin) {

                # Window already open; draw attention to the fact by 'present'ing it
                $axmud::CLIENT->aboutWin->restoreFocus();
                # Open it at the right page
                $axmud::CLIENT->aboutWin->notebook->set_current_page(3);

            } else {

                # Open the About window
                my $winObj = $self->winObj->quickFreeWin(
                    'Games::Axmud::OtherWin::About',
                    $self->winObj->visibleSession,
                    # Config
                    'first_tab' => 'license',
                );

                if ($winObj) {

                    # Only one About window can be open at a time
                    $axmud::CLIENT->set_aboutWin($winObj);
                }
            }
        });
        $menuColumn_help->append($menuItem_license);
        # (Desensitised only when the setup wizwin is open)
        $self->ivAdd('menuItemHash', 'license', $menuItem_license);

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

        my $menuItem_website = Gtk3::MenuItem->new($axmud::SCRIPT . ' _website...');
        $menuItem_website->signal_connect('activate' => sub {

            $axmud::CLIENT->openURL($axmud::URL);
        });
        $menuColumn_help->append($menuItem_website);
        # (Requires GA::Client->browserCmd)
        $self->ivAdd('menuItemHash', 'go_website', $menuItem_website);

        # Setup complete
        return $menuColumn_help;
    }

    sub addPluginWidgets {

        # Called by GA::Client->addPluginMenus to add a sub-menu in the 'plugins' column, for the
        #   benefit of a loaded plugin
        # Also called by $self->objEnable to add plugin sub-menus for 'internal' windows that are
        #   created after the plugins are loaded
        #
        # Expected arguments
        #   $plugin     - The plugin name
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise, returns the sub-menu to which the plugin's code can add new menu items

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

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

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

        # Create a sub-menu for this plugin. If this is the first plugin to add menu items, add a
        #   separator
        if (! $self->pluginHash) {

            $self->pluginMenu->append(Gtk3::SeparatorMenuItem->new());
        }

        # Add a sub-menu
        my $subMenu = Gtk3::Menu->new();

        my $menuItem = Gtk3::MenuItem->new('_' . $plugin . ' plugin');
        $menuItem->set_submenu($subMenu);
        $self->pluginMenu->append($menuItem);

        # Update IVs
        $self->ivAdd('pluginHash', $plugin, $subMenu);
        $self->ivAdd('pluginMenuItemHash', $plugin, $menuItem);

        return $subMenu;
    }

    sub sensitiseWidgets {

        # Can be called by anything, but usually called by GA::Win::Internal->restrictMenuBars
        # Given a list of Gtk3 widgets (all of them menu bar items), sets them as sensitive
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   @widgetList - A list of widget names, matching keys in $self->menuItemHash
        #                   (e.g. 'move_up_level')
        #
        # Return values
        #   1

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

        # (No improper arguments to check)

        foreach my $widgetName (@widgetList) {

            my $widget = $self->ivShow('menuItemHash', $widgetName);
            if ($widget) {

                $widget->set_sensitive(TRUE);
            }
        }

        return 1;
    }

    sub desensitiseWidgets {

        # Can be called by anything, but usually called by GA::Win::Internal->restrictMenuBars
        # Given a list of Gtk3 widgets (all of them menu bar items), sets them as insensitive
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   @widgetList - A list of widget names, matching keys in $self->menuItemHash
        #                   (e.g. 'move_up_level')
        #
        # Return values
        #   1

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

        # (No improper arguments to check)

        foreach my $widgetName (@widgetList) {

            my $widget = $self->ivShow('menuItemHash', $widgetName);
            if ($widget) {

                $widget->set_sensitive(FALSE);
            }
        }

        return 1;
    }

    # Menu bar support functions

    sub promptUser {

        # Called by most menu items in $self->drawWorldColumn
        # Before doing an action that will lead to the current connection being closed (e.g. 'Stop
        #   client', which runs the ';stopclient' command), prompt the user for confirmation
        # We don't want the user to lose their connection after accidentally clicking on a menu
        #   item; at the same time, we usually don't ask for confirmation when the user types the
        #   equivalent client command, because they are much less likely to make a mistake while
        #   typing, than while clicking
        #
        # Expected arguments
        #   $title  - The 'dialogue' window title
        #   $msg    - A message to display in the 'dialogue' window
        #
        # Return values
        #   'undef' on improper arguments or if the user clicks 'No' in the 'dialogue' window (or
        #       closes it without clicking 'Yes'
        #   1 if the user clicks on 'Yes' in the 'dialogue' window

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

        # Local variables
        my $choice;

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

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

        # Prompt the user
        $choice = $self->winObj->showMsgDialogue(
            $title,
            'question',
            $msg,
            'yes-no',
        );

        if ($choice eq 'yes') {
            return 1;
        } else {
            return undef;
        }
    }

    sub getWorldList {

        # Called by $self->drawFileColumn
        # Compiles an ordered list of world profile names, with the visible session's current world
        #   as the first item in the list
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, the sorted list of world profile names

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

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

        @list = sort {
            if ($a->fileType ne $b->fileType) {
                $a->fileType cmp $b->fileType
            } else {
                lc($a->name) cmp lc($b->name)
            }
        } (values %regHash);

        foreach my $obj (@list) {

            my $string;

            if ($obj->fileType eq 'worldprof') {
                $string = $obj->name . ' (type: worldprof)';
            } else {
                $string = $obj->name;
            }

            # Don't add the same file object more than once
            if (! exists $checkHash{$obj}) {

                push (@sortedList, $string);
                $returnHash{$string} = $obj;
                $checkHash{$obj} = undef;
            }
        }

        # Get a list of session file objects, and sort them alphabetically
        %regHash = $self->winObj->visibleSession->sessionFileObjHash;
        @list = sort {lc($a->name) cmp lc($b->name)} (values %regHash);

        foreach my $obj (@list) {

            my $string = $obj->name . ' (parent: ' . $obj->assocWorldProf . ')';

            # Don't add the same file object more than once
            if (! exists $checkHash{$obj}) {

                push (@sortedList, $string);
                $returnHash{$string} = $obj->name;
                $checkHash{$obj} = undef;
            }
        }

        # Operation complete
        return (\@sortedList, \%returnHash);
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    sub menuBar
        { $_[0]->{menuBar} }

    sub menuItemHash
        { my $self = shift; return %{$self->{menuItemHash}}; }

    sub pluginMenu
        { $_[0]->{pluginMenu} }
    sub pluginHash
        { my $self = shift; return %{$self->{pluginHash}}; }
    sub pluginMenuItemHash
        { my $self = shift; return %{$self->{pluginMenuItemHash}}; }

    sub saveAllSessionsFlag
        { $_[0]->{saveAllSessionsFlag} }
}

{ package Games::Axmud::Strip::Toolbar;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(Games::Axmud::Generic::Strip Games::Axmud);

    ##################
    # Constructors

    sub new {

        # Called by GA::Win::Internal->drawWidgets or ->addStripObj
        # Creates the GA::Strip::Toolbar - a non-compulsory strip object containing a Gtk3::Toolbar
        #
        # Expected arguments
        #   $number     - The strip object's number within the parent window (matches
        #                   GA::Win::Internal->stripCount, or -1 for a temporary strip object
        #                   created to access its default IVs)
        #   $winObj     - The parent window object (GA::Win::Internal). 'temp' for temporary strip
        #                   objects
        #
        # Optional arguments
        #   %initHash   - A hash containing arbitrary data to use as the strip object's
        #                   initialisation settings. The strip object should use default
        #                   initialisation settings unless it can succesfully interpret one or more
        #                   of the key-value pairs in the hash, if there are any
        #               - (This type of strip object requires no initialisation settings)
        #
        # Return values
        #   'undef' on improper arguments
        #   Blessed reference to the newly-created object on success

        my ($class, $number, $winObj, %initHash) = @_;

        # Local variables
        my %modHash;

        # Check for improper arguments
        if (! defined $class || ! defined $number || ! defined $winObj) {

            return $axmud::CLIENT->writeImproper($class . '->new', @_);
        }

        # Setup
        my $self = {
            _objName                    => 'strip_' . $number,
            _objClass                   => $class,
            _parentFile                 => undef,       # No parent file object
            _parentWorld                => undef,       # No parent file object
            _privFlag                   => TRUE,        # All IVs are private

            # Standard strip object IVs
            # -------------------------

            # The strip object's number within the parent window (matches
            #   GA::Win::Internal->stripCount, or -1 for a temporary strip object created to access
            #   its default IVs)
            number                      => $number,
            # The type of strip object (custom strip objects should use a ->type starting with
            #   'custom_' to avoid clashing with future built-in strip objects)
            type                        => 'toolbar',
            # The parent window object (GA::Win::Internal). 'temp' for temporary strip objects
            winObj                      => $winObj,

            # Flag set to TRUE if the strip object is visible (has actually drawn widgets in the
            #   window), set to FALSE if it is not visible (has drawn no widgets in the window, but
            #   still exists in GA::Win::Internal->stripHash, etc)
            # The flag might be set to FALSE in strip objects like GA::Strip::GaugeBox, which might
            #   have gauges to draw, or not, depending on current conditions. (Most strip objects
            #   have this flag set to TRUE all the time)
            # If FALSE, GA::Win::Internal->drawWidgets and ->addStripObj don't draw any widgets when
            #   called by this object's functions
            # NB Strip objects are created with this flag set to TRUE or FALSE, but once created,
            #   the flag's value shouldn't be modified by anything other than
            #   GA::Win::Internal->hideStripObj and ->revealStripObj (which in turn call
            #   $self->set_visibleFlag)
            visibleFlag                 => TRUE,
            # Flag set to TRUE is the strip object should be given its share of any extra space
            #   within the packing box (the extra space is divided equally between all children of
            #   the box whose ->expandFlag is TRUE)
            expandFlag                  => FALSE,
            # Flag set to TRUE if any space given to the strip object by the 'expand' option is
            #   actually allocated within the strip object, FALSE if it is used as padding outside
            #   it (on both sides)
            fillFlag                    => FALSE,
            # Flag set to TRUE if the strip object should be packed into its window with a small
            #   gap between strip objects to either side; FALSE if not (can be set to FALSE if the
            #   the strip object's widgets are drawn in a way, such that a gap is not necessary,
            #   for example in the toolbar strip object)
            spacingFlag                 => FALSE,
            # Flag set to TRUE if only one instance of this strip object should be added to the
            #   parent window, set to FALSE if any number of instances can be added
            jealousyFlag                => TRUE,
            # Flag set to TRUE if this strip object can be added when $axmud::BLIND_MODE_FLAG is
            #   TRUE, FALSE if it can't be added (because it's not useful for visually-impaired
            #   users)
            blindFlag                   => FALSE,
            # Flag set to TRUE if the main container widget, stored in $self->packingBox, should be
            #   allowed to accept the focus, FALSE if not. The restriction is applied during the
            #   call to GA::Win::Internal->drawWidgets and ->addStripObj. Even if FALSE, widgets in
            #   the container widget can be set to accept the focus (e.g. the Gtk3::Entry in
            #   GA::Strip::MenuBar)
            allowFocusFlag              => FALSE,

            # Initialisation settings stored as a hash (see the comments above)
            initHash                    => \%modHash,
            # Reference to a function to call when some widget is used. This IV is set only when
            #   required by this type of strip object. It can be set by a call to
            #   $self->set_func() or by some setting in $self->initHash, which is applied in the
            #   call to $self->objEnable(). To obtain a reference to an OOP method, you can use the
            #   generic object function Games::Axmud->getMethodRef()
            funcRef                     => undef,
            # A value passed to ->funcRef when it is called which identifies this strip object and
            #   its widget(s). Can be any value, including 'undef'. It can be set by a call to
            #   $self->set_id() or by some setting in $self->initHash, which is applied in the call
            #   to $self->objEnable()
            funcID                      => undef,

            # The container widget for this strip object (usually a Gtk3::HBox or Gtk3::VBox). This
            #   widget is the one added to the window's main Gtk3::HBox or Gtk3::VBox
            packingBox                  => undef,       # Gtk3::VBox

            # Other IVs
            # ---------

            # Widgets
            toolbar                     => undef,       # Gtk3::Toolbar
            toolbarWidgetList           => [],          # Various

            # Toolbar buttons which will be sensitised or desensitised, depending on whether the
            #   session is connected to a world (or if it's in 'offline' mode). Hash in the form
            #       $requireConnectHash{'button_name'} = Gtk3_widget
            requireConnectHash          => {},
            # Toolbar buttons which will be sensitised or desensitised, depending on whether there
            #   is a visible session for this window. Hash in the form
            #       $requireSessionHash{'button_name'} = Gtk3_widget
            requireSessionHash          => {},
        };

        # Bless the object into existence
        bless $self, $class;

        return $self;
    }

    ##################
    # Methods

    # Standard strip object functions

    sub objEnable {

        # Called by GA::Win::Internal->drawWidgets or ->addStripObj
        # Sets up the strip object's widgets
        #
        # Expected arguments
        #   $winmapObj  - The winmap object (GA::Obj::Winmap) that specifies the layout of the
        #                   parent window
        #
        # Return values
        #   'undef' on improper arguments
        #   1 on success

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

        }

        # Setup
        my $self = {
            _objName                    => 'strip_' . $number,
            _objClass                   => $class,
            _parentFile                 => undef,       # No parent file object
            _parentWorld                => undef,       # No parent file object
            _privFlag                   => TRUE,        # All IVs are private

            # Standard strip object IVs
            # -------------------------

            # The strip object's number within the parent window (matches
            #   GA::Win::Internal->stripCount, or -1 for a temporary strip object created to access
            #   its default IVs)
            number                      => $number,
            # The type of strip object (custom strip objects should use a ->type starting with
            #   'custom_' to avoid clashing with future built-in strip objects)
            type                        => 'table',
            # The parent window object (GA::Win::Internal). 'temp' for temporary strip objects
            winObj                      => $winObj,

            # Flag set to TRUE if the strip object is visible (has actually drawn widgets in the
            #   window), set to FALSE if it is not visible (has drawn no widgets in the window, but
            #   still exists in GA::Win::Internal->stripHash, etc)
            # The flag might be set to FALSE in strip objects like GA::Strip::GaugeBox, which might
            #   have gauges to draw, or not, depending on current conditions. (Most strip objects
            #   have this flag set to TRUE all the time)
            # If FALSE, GA::Win::Internal->drawWidgets and ->addStripObj don't draw any widgets when
            #   called by this object's functions
            # NB Strip objects are created with this flag set to TRUE or FALSE, but once created,
            #   the flag's value shouldn't be modified by anything other than
            #   GA::Win::Internal->hideStripObj and ->revealStripObj (which in turn call
            #   $self->set_visibleFlag)
            visibleFlag                 => TRUE,
            # Flag set to TRUE is the strip object should be given its share of any extra space
            #   within the packing box (the extra space is divided equally between all children of
            #   the box whose ->expandFlag is TRUE)
            expandFlag                  => TRUE,
            # Flag set to TRUE if any space given to the strip object by the 'expand' option is
            #   actually allocated within the strip object, FALSE if it is used as padding outside
            #   it (on both sides)
            fillFlag                    => TRUE,
            # Flag set to TRUE if the strip object should be packed into its window with a small
            #   gap between strip objects to either side; FALSE if not (can be set to FALSE if the
            #   the strip object's widgets are drawn in a way, such that a gap is not necessary,
            #   for example in the toolbar strip object)
            spacingFlag                 => TRUE,
            # Flag set to TRUE if only one instance of this strip object should be added to the
            #   parent window, set to FALSE if any number of instances can be added
            jealousyFlag                => TRUE,
            # Flag set to TRUE if this strip object can be added when $axmud::BLIND_MODE_FLAG is
            #   TRUE, FALSE if it can't be added (because it's not useful for visually-impaired
            #   users)
            blindFlag                   => TRUE,
            # Flag set to TRUE if the main container widget, stored in $self->packingBox, should be
            #   allowed to accept the focus, FALSE if not. The restriction is applied during the
            #   call to GA::Win::Internal->drawWidgets and ->addStripObj. Even if FALSE, widgets in
            #   the container widget can be set to accept the focus (e.g. the Gtk3::Entry in
            #   GA::Strip::MenuBar)
            allowFocusFlag              => FALSE,

            # Initialisation settings stored as a hash (see the comments above)
            initHash                    => \%modHash,
            # Reference to a function to call when some widget is used. This IV is set only when
            #   required by this type of strip object. It can be set by a call to
            #   $self->set_func() or by some setting in $self->initHash, which is applied in the
            #   call to $self->objEnable(). To obtain a reference to an OOP method, you can use the
            #   generic object function Games::Axmud->getMethodRef()
            funcRef                     => undef,
            # A value passed to ->funcRef when it is called which identifies this strip object and
            #   its widget(s). Can be any value, including 'undef'. It can be set by a call to
            #   $self->set_id() or by some setting in $self->initHash, which is applied in the call
            #   to $self->objEnable()
            funcID                      => undef,

            # The container widget for this strip object (usually a Gtk3::HBox or Gtk3::VBox). This
            #   widget is the one added to the window's main Gtk3::HBox or Gtk3::VBox
            packingBox                  => undef,       # Gtk3::ScrolledWindow

            # Other IVs
            # ---------

            # Gtk3 widgets to draw the table
            table                       => undef,       # Gtk3::Grid

            # The table size actually used
            tableSize                   => undef,

            # Hash of tablezone objects (GA::Obj::Tablezone), each of which marks out an area of
            #   Gtk3::Grid for a single table object. Hash in the form
            #       $zoneHash{number} = blessed_reference_to_tablezone_object
            tablezoneHash               => {},
            # Number of tablezones ever created for this Gtk3::Grid (used to give every tablezone
            #   object a number unique to the Gtk3::Grid)
            tablezoneCount              => 0,
            # Hash of table objects (inheriting from GA::Generic::Table) which occupy the space
            #   marked out by a single tablezone. Hash in the form
            #       $tableObjHash{number} = blessed_reference_to_table_object
            tableObjHash                => {},
            # Number of table objects ever created for this Gtk3::Grid (used to give every table
            #   object a number unique to the Gtk3::Grid)
            tableObjCount               => 0,

            # The current value for spacing between table objects (actually applied as a border
            #   around each table object; set from $self->initHash by $self->objEnable)
            currentSpacing              => 0,
            # The current value for the border at the edge of the table (set from $self->initHash by
            #   $self->objEnable)
            currentBorder               => 0,
        };

        # Bless the object into existence
        bless $self, $class;

        return $self;
    }

    ##################
    # Methods

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

        }

        # Setup
        my $self = {
            _objName                    => 'strip_' . $number,
            _objClass                   => $class,
            _parentFile                 => undef,       # No parent file object
            _parentWorld                => undef,       # No parent file object
            _privFlag                   => TRUE,        # All IVs are private

            # Standard strip object IVs
            # -------------------------

            # The strip object's number within the parent window (matches
            #   GA::Win::Internal->stripCount, or -1 for a temporary strip object created to access
            #   its default IVs)
            number                      => $number,
            # The type of strip object (custom strip objects should use a ->type starting with
            #   'custom_' to avoid clashing with future built-in strip objects)
            type                        => 'gauge_box',
            # The parent window object (GA::Win::Internal). 'temp' for temporary strip objects
            winObj                      => $winObj,

            # Flag set to TRUE if the strip object is visible (has actually drawn widgets in the
            #   window), set to FALSE if it is not visible (has drawn no widgets in the window, but
            #   still exists in GA::Win::Internal->stripHash, etc)
            # The flag might be set to FALSE in strip objects like GA::Strip::GaugeBox, which might
            #   have gauges to draw, or not, depending on current conditions. (Most strip objects
            #   have this flag set to TRUE all the time)
            # If FALSE, GA::Win::Internal->drawWidgets and ->addStripObj don't draw any widgets when
            #   called by this object's functions
            # NB Strip objects are created with this flag set to TRUE or FALSE, but once created,
            #   the flag's value shouldn't be modified by anything other than
            #   GA::Win::Internal->hideStripObj and ->revealStripObj (which in turn call
            #   $self->set_visibleFlag)
            visibleFlag                 => FALSE,      # Wait until the first gauge is drawn
            # Flag set to TRUE is the strip object should be given its share of any extra space
            #   within the packing box (the extra space is divided equally between all children of
            #   the box whose ->expandFlag is TRUE)
            expandFlag                  => FALSE,
            # Flag set to TRUE if any space given to the strip object by the 'expand' option is
            #   actually allocated within the strip object, FALSE if it is used as padding outside
            #   it (on both sides)
            fillFlag                    => TRUE,       # Force canvas to use available width
            # Flag set to TRUE if the strip object should be packed into its window with a small
            #   gap between strip objects to either side; FALSE if not (can be set to FALSE if the
            #   the strip object's widgets are drawn in a way, such that a gap is not necessary,
            #   for example in the toolbar strip object)
            spacingFlag                 => TRUE,
            # Flag set to TRUE if only one instance of this strip object should be added to the
            #   parent window, set to FALSE if any number of instances can be added
            jealousyFlag                => TRUE,
            # Flag set to TRUE if this strip object can be added when $axmud::BLIND_MODE_FLAG is
            #   TRUE, FALSE if it can't be added (because it's not useful for visually-impaired
            #   users)
            blindFlag                   => FALSE,
            # Flag set to TRUE if the main container widget, stored in $self->packingBox, should be
            #   allowed to accept the focus, FALSE if not. The restriction is applied during the
            #   call to GA::Win::Internal->drawWidgets and ->addStripObj. Even if FALSE, widgets in
            #   the container widget can be set to accept the focus (e.g. the Gtk3::Entry in
            #   GA::Strip::MenuBar)
            allowFocusFlag              => FALSE,

            # Initialisation settings stored as a hash (see the comments above)
            initHash                    => \%modHash,
            # Reference to a function to call when some widget is used. This IV is set only when
            #   required by this type of strip object. It can be set by a call to
            #   $self->set_func() or by some setting in $self->initHash, which is applied in the
            #   call to $self->objEnable(). To obtain a reference to an OOP method, you can use the
            #   generic object function Games::Axmud->getMethodRef()
            funcRef                     => undef,
            # A value passed to ->funcRef when it is called which identifies this strip object and
            #   its widget(s). Can be any value, including 'undef'. It can be set by a call to
            #   $self->set_id() or by some setting in $self->initHash, which is applied in the call
            #   to $self->objEnable()
            funcID                      => undef,

            # The container widget for this strip object (usually a Gtk3::HBox or Gtk3::VBox). This
            #   widget is the one added to the window's main Gtk3::HBox or Gtk3::VBox
            packingBox                  => undef,       # Gtk3::VBox

            # Other IVs
            # ---------

            # Widgets
            frame                       => undef,       # Gtk3::Frame
            canvas                      => undef,       # GooCanvas2::Canvas
            # A list of GooCanvas2::CanvasRect and GooCanvas2::CanvasText objects drawn in the gauge
            #   box, which are destroyed every time $self->drawGauges is called
            gaugeCanvasList             => [],          # GooCanvas2::CanvasRect/::CanvasText

            # The size of the available area inside the frame containing the canvas, set whenever
            #   the frame's size-allocate signal is emitted (this is the only way to guarantee that
            #   that the correct size is available to $self->drawGauges)
            canvasFrameWidth            => 1,
            canvasFrameHeight           => 1,

            # The size of the GooCanvas2::Canvas used to display gauges (width is the same as the
            #   available space, so only height specified here)
            # The height (in pixels) of gauges. The total height of the gauge will be this value,
            #   plus the spacing above and below it. (Width is the same as the available space, so
            #   only height specified here)
            gaugeHeight                 => 16,
            # The size of the rounded corner (in pixels)
            gaugeCorner                 => 5,
            # Spacing (padding) used between the gauges, and between the edges of the gauge box
            gaugeSpacingX               => 8,
            gaugeSpacingY               => 4,
            # The font and size to use for gauge labels. These default values produce a font that's
            #   about the right the size for a gauge of height $self->gaugeHeight
            gaugeLabelFont              => 'Monospace',
            gaugeLabelFontSize          => 10,
            # Registry hash of GA::Obj::GaugeLevel objects, one for each gauge level drawn in the
            #   gauge box for a single session
            # Hash in the form
            #   $gaugeLevelHash{unique_number} = gauge_level_object
            # ...where 'unique_number' is unique across all sessions
            gaugeLevelHash              => {},
            # The number of gauge levels ever created (used to provide the unique number)
            gaugeLevelCount             => 0,
            # Maximum number of gauge levels (sanity check)

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

        }

        # Setup
        my $self = {
            _objName                    => 'strip_' . $number,
            _objClass                   => $class,
            _parentFile                 => undef,       # No parent file object
            _parentWorld                => undef,       # No parent file object
            _privFlag                   => TRUE,        # All IVs are private

            # Standard strip object IVs
            # -------------------------

            # The strip object's number within the parent window (matches
            #   GA::Win::Internal->stripCount, or -1 for a temporary strip object created to access
            #   its default IVs)
            number                      => $number,
            # The type of strip object (custom strip objects should use a ->type starting with
            #   'custom_' to avoid clashing with future built-in strip objects)
            type                        => 'search_box',
            # The parent window object (GA::Win::Internal). 'temp' for temporary strip objects
            winObj                      => $winObj,

            # Flag set to TRUE if the strip object is visible (has actually drawn widgets in the
            #   window), set to FALSE if it is not visible (has drawn no widgets in the window, but
            #   still exists in GA::Win::Internal->stripHash, etc)
            # The flag might be set to FALSE in strip objects like GA::Strip::GaugeBox, which might
            #   have gauges to draw, or not, depending on current conditions. (Most strip objects
            #   have this flag set to TRUE all the time)
            # If FALSE, GA::Win::Internal->drawWidgets and ->addStripObj don't draw any widgets when
            #   called by this object's functions
            # NB Strip objects are created with this flag set to TRUE or FALSE, but once created,
            #   the flag's value shouldn't be modified by anything other than
            #   GA::Win::Internal->hideStripObj and ->revealStripObj (which in turn call
            #   $self->set_visibleFlag)
            visibleFlag                 => FALSE,      # Wait until the search box needs to be drawn
            # Flag set to TRUE is the strip object should be given its share of any extra space
            #   within the packing box (the extra space is divided equally between all children of
            #   the box whose ->expandFlag is TRUE)
            expandFlag                  => FALSE,
            # Flag set to TRUE if any space given to the strip object by the 'expand' option is
            #   actually allocated within the strip object, FALSE if it is used as padding outside
            #   it (on both sides)
            fillFlag                    => TRUE,       # Force canvas to use available width
            # Flag set to TRUE if the strip object should be packed into its window with a small
            #   gap between strip objects to either side; FALSE if not (can be set to FALSE if the
            #   the strip object's widgets are drawn in a way, such that a gap is not necessary,
            #   for example in the toolbar strip object)
            spacingFlag                 => TRUE,
            # Flag set to TRUE if only one instance of this strip object should be added to the
            #   parent window, set to FALSE if any number of instances can be added
            jealousyFlag                => TRUE,
            # Flag set to TRUE if this strip object can be added when $axmud::BLIND_MODE_FLAG is
            #   TRUE, FALSE if it can't be added (because it's not useful for visually-impaired
            #   users)
            blindFlag                   => FALSE,
            # Flag set to TRUE if the main container widget, stored in $self->packingBox, should be
            #   allowed to accept the focus, FALSE if not. The restriction is applied during the
            #   call to GA::Win::Internal->drawWidgets and ->addStripObj. Even if FALSE, widgets in
            #   the container widget can be set to accept the focus (e.g. the Gtk3::Entry in
            #   GA::Strip::MenuBar)
            allowFocusFlag              => FALSE,

            # Initialisation settings stored as a hash (see the comments above)
            initHash                    => \%modHash,
            # Reference to a function to call when some widget is used. This IV is set only when
            #   required by this type of strip object. It can be set by a call to
            #   $self->set_func() or by some setting in $self->initHash, which is applied in the
            #   call to $self->objEnable(). To obtain a reference to an OOP method, you can use the
            #   generic object function Games::Axmud->getMethodRef()
            funcRef                     => undef,
            # A value passed to ->funcRef when it is called which identifies this strip object and
            #   its widget(s). Can be any value, including 'undef'. It can be set by a call to
            #   $self->set_id() or by some setting in $self->initHash, which is applied in the call
            #   to $self->objEnable()
            funcID                      => undef,

            # The container widget for this strip object (usually a Gtk3::HBox or Gtk3::VBox). This
            #   widget is the one added to the window's main Gtk3::HBox or Gtk3::VBox
            packingBox                  => undef,       # Gtk3::VBox

            # Other IVs
            # ---------

            # Widgets
            findEntry                   => undef,       # Gtk3::Entry
            prevButton                  => undef,       # Gtk3::Button
            nextButton                  => undef,       # Gtk3::Button
            resetButton                 => undef,       # Gtk3::Button
            caseButton                  => undef,       # Gtk3::ToggleButton
            regexButton                 => undef,       # Gtk3::ToggleButton
            splitButton                 => undef,       # Gtk3::ToggleButton

            # Flags set to TRUE when the 'Case senstive', 'Use regex' and 'Split screen' buttons are
            #   selected, FALSE when they are not selected
            caseFlag                    => FALSE,
            regexFlag                   => FALSE,
            splitFlag                   => FALSE,

            # The number of the textview object (GA::Obj::TextView) that was last search; required
            #   in case the window's current pane is changed (when the user clicks a button in the
            #   entry strip object)
            textViewNum                 => undef,
        };

        # Bless the object into existence
        bless $self, $class;

        return $self;
    }

    ##################
    # Methods

    # Standard strip object functions

    sub objEnable {

        # Called by GA::Win::Internal->drawWidgets or ->addStripObj
        # Sets up the strip object's widgets
        #

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

        }

        # Setup
        my $self = {
            _objName                    => 'strip_' . $number,
            _objClass                   => $class,
            _parentFile                 => undef,       # No parent file object
            _parentWorld                => undef,       # No parent file object
            _privFlag                   => TRUE,        # All IVs are private

            # Standard strip object IVs
            # -------------------------

            # The strip object's number within the parent window (matches
            #   GA::Win::Internal->stripCount, or -1 for a temporary strip object created to
            #   access its default IVs)
            number                      => $number,
            # The type of strip object (custom strip objects should use a ->type starting with
            #   'custom_' to avoid clashing with future built-in strip objects)
            type                        => 'entry',
            # The parent window object (GA::Win::Internal). 'temp' for temporary strip objects
            winObj                      => $winObj,

            # Flag set to TRUE if the strip object is visible (has actually drawn widgets in the
            #   window), set to FALSE if it is not visible (has drawn no widgets in the window, but
            #   still exists in GA::Win::Internal->stripHash, etc)
            # The flag might be set to FALSE in strip objects like GA::Strip::GaugeBox, which might
            #   have gauges to draw, or not, depending on current conditions. (Most strip objects
            #   have this flag set to TRUE all the time)
            # If FALSE, GA::Win::Internal->drawWidgets and ->addStripObj don't draw any widgets when
            #   called by this object's functions
            # NB Strip objects are created with this flag set to TRUE or FALSE, but once created,
            #   the flag's value shouldn't be modified by anything other than
            #   GA::Win::Internal->hideStripObj and ->revealStripObj (which in turn call
            #   $self->set_visibleFlag)
            visibleFlag                 => TRUE,
            # Flag set to TRUE is the strip object should be given its share of any extra space
            #   within the packing box (the extra space is divided equally between all children of
            #   the box whose ->expandFlag is TRUE)
            expandFlag                  => FALSE,
            # Flag set to TRUE if any space given to the strip object by the 'expand' option is
            #   actually allocated within the strip object, FALSE if it is used as padding outside
            #   it (on both sides)
            fillFlag                    => FALSE,
            # Flag set to TRUE if the strip object should be packed into its window with a small
            #   gap between strip objects to either side; FALSE if not (can be set to FALSE if the
            #   the strip object's widgets are drawn in a way, such that a gap is not necessary,
            #   for example in the toolbar strip object)
            spacingFlag                 => TRUE,
            # Flag set to TRUE if only one instance of this strip object should be added to the
            #   parent window, set to FALSE if any number of instances can be added
            jealousyFlag                => TRUE,
            # Flag set to TRUE if this strip object can be added when $axmud::BLIND_MODE_FLAG is
            #   TRUE, FALSE if it can't be added (because it's not useful for visually-impaired
            #   users)
            blindFlag                   => TRUE,
            # Flag set to TRUE if the main container widget, stored in $self->packingBox, should be
            #   allowed to accept the focus, FALSE if not. The restriction is applied during the
            #   call to GA::Win::Internal->drawWidgets and ->addStripObj. Even if FALSE, widgets in
            #   the container widget can be set to accept the focus (e.g. the Gtk3::Entry in
            #   GA::Strip::MenuBar)
            allowFocusFlag              => FALSE,

            # Initialisation settings stored as a hash (see the comments above)
            initHash                    => \%modHash,
            # Reference to a function to call when some widget is used. This IV is set only when
            #   required by this type of strip object. It can be set by a call to
            #   $self->set_func() or by some setting in $self->initHash, which is applied in the
            #   call to $self->objEnable(). To obtain a reference to an OOP method, you can use the
            #   generic object function Games::Axmud->getMethodRef()
            funcRef                     => undef,
            # A value passed to ->funcRef when it is called which identifies this strip object and
            #   its widget(s). Can be any value, including 'undef'. It can be set by a call to
            #   $self->set_id() or by some setting in $self->initHash, which is applied in the call
            #   to $self->objEnable()
            funcID                      => undef,

            # The container widget for this strip object (usually a Gtk3::HBox or Gtk3::VBox). This
            #   widget is the one added to the window's main Gtk3::HBox or Gtk3::VBox
            packingBox                  => undef,       # Gtk3::HBox

            # Other IVs
            # ---------

            # Widgets
            entry                       => undef,       # Gtk3::Entry
            preEntry                    => undef,       # Gtk3::Entry
            postEntry                   => undef,       # Gtk3::Entry
            wipeButton                  => undef,       # Gtk3::ToolButton
            addButton                   => undef,       # Gtk3::ToolButton
            consoleButton               => undef,       # Gtk3::ToolButton
            inputButton                 => undef,       # Gtk3::ToolButton
            searchButton                => undef,       # Gtk3::ToolButton
            cancelButton                => undef,       # Gtk3::ToolButton
            switchButton                => undef,       # Gtk3::ToolButton
            scrollButton                => undef,       # Gtk3::ToolButton
            splitButton                 => undef,       # Gtk3::ToolButton

            # List of currently-existing pane objects. New pane objects are added to the end of the
            #   list. The first pane object in the list is the one to which the scroll and split
            #   buttons apply. Clicking the switch buttons moves the first pane in the list to the
            #   end, so that the next pane object becomes the one to which the scroll and split
            #   buttons apply
            # Keypresses captured by GA::Win::Internal are also applied to the first pane object
            #   in the list
            paneObjList                 => [],

            # The strip can contain one, two or three entry boxes; a main one (stored in
            #   $self->entry), a small box whose contents is prepended to every world commmand
            #   ($self->preEntry), and a small box whose contents is appended to every world
            #   command ($self->postEntry). Click $self->addButton cycles through different
            #   combinations of these boxes; this IV stores the state we're in at the moment:
            #       'default'   - show only the main entry box
            #       'pre'       - show the prepending and main entry boxes
            #       'post'      - show the main and appending entry boxes
            #       'both'      - show all three boxes
            entryBoxMode                => 'default',
            # For 'main' windows, when GA::Client->autoCompleteMode = 'auto', the first time the
            #   user presses the 'up' or 'down' arrow key, this IV is set to the contents of the
            #   entry box (even if it's an empty string). The IV is set back to 'undef' as soon as
            #   the user presses the ENTER key

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   $flag   - Flag set to TRUE if the entry box should be obscured, FALSE (or 'undef') if it
        #               should be shown
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        if ($self->entry) {

            $self->entry->set_text('');
            if ($flag) {
                $self->entry->set_visibility(FALSE);
            } else {
                $self->entry->set_visibility(TRUE);
            }
        }

        if ($self->preEntry) {

            $self->preEntry->set_text('');
            if ($flag) {
                $self->preEntry->set_visibility(FALSE);
            } else {
                $self->preEntry->set_visibility(TRUE);
            }
        }

        if ($self->postEntry) {

            $self->postEntry->set_text('');
            if ($flag) {
                $self->postEntry->set_visibility(FALSE);
            } else {
                $self->postEntry->set_visibility(TRUE);
            }
        }

        # (If $self->entry doesn't exist, the other two entry boxes don't exist either)
        if ($self->entry) {

            $self->winObj->winShowAll($self->_objClass . '->obscureEntry');
        }

        return 1;
    }

    sub commandeerEntry {

        # Called by GA::Obj::TextView->setButtonPressEvent and ->createPopupMenu
        # When an MXP <SEND>..</SEND> construction (or any other code) wants to insert a command
        #   into the client's command line (instead of sending it to the world directly), this
        #   function is called
        # The command is copied into the entry box (but only if the calling session is this window's
        #   visible session, which it almost certainly is)
        #
        # Expected arguments
        #   $session        - The calling GA::Session
        #   $cmd            - The command to display in the entry box
        #
        # Return values
        #   'undef' on improper arguments, if $session is not this window's visible session or if
        #       the $cmd is not displayed
        #   1 if the $cmd is displayed

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

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

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

        if (! $self->winObj->visibleSession || $self->winObj->visibleSession ne $session) {

            # Do nothing
            return undef;

        } else {

            $self->entry->set_text($cmd);
            $self->entry->set_visibility(TRUE);
            $self->entry->grab_focus();

            $self->winObj->winShowAll($self->_objClass . '->commandeerEntry');

            return 1;
        }
    }

    sub applyBackspace {

        # Called by GA::Win::Internal->setKeyPressEvent when the user presses the backspace key in
        #   special echo mode
        # $self->specialWorldCmd stores a copy of the world command that's being sent to the world,
        #   one character at a time; amend the copy by removing its final character
        # (This doesn't guarantee that the world command stored in ->specialWorldCmd is exactly the
        #   same as the command the world thinks it's receiving, but it should be close enough)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

lib/Games/Axmud/Strip.pm  view on Meta::CPAN

        }

        # Setup
        my $self = {
            _objName                    => 'strip_' . $number,
            _objClass                   => $class,
            _parentFile                 => undef,       # No parent file object
            _parentWorld                => undef,       # No parent file object
            _privFlag                   => TRUE,        # All IVs are private

            # Standard strip object IVs
            # -------------------------

            # The strip object's number within the parent window (matches
            #   GA::Win::Internal->stripCount, or -1 for a temporary strip object created to access
            #   its default IVs)
            number                      => $number,
            # The type of strip object (custom strip objects should use a ->type starting with
            #   'custom_' to avoid clashing with future built-in strip objects)
            type                        => 'connect_info',
            # The parent window object (GA::Win::Internal). 'temp' for temporary strip objects
            winObj                      => $winObj,

            # Flag set to TRUE if the strip object is visible (has actually drawn widgets in the
            #   window), set to FALSE if it is not visible (has drawn no widgets in the window, but
            #   still exists in GA::Win::Internal->stripHash, etc)
            # The flag might be set to FALSE in strip objects like GA::Strip::GaugeBox, which might
            #   have gauges to draw, or not, depending on current conditions. (Most strip objects
            #   have this flag set to TRUE all the time)
            # If FALSE, GA::Win::Internal->drawWidgets and ->addStripObj don't draw any widgets when
            #   called by this object's functions
            # NB Strip objects are created with this flag set to TRUE or FALSE, but once created,
            #   the flag's value shouldn't be modified by anything other than
            #   GA::Win::Internal->hideStripObj and ->revealStripObj (which in turn call
            #   $self->set_visibleFlag)
            visibleFlag                 => TRUE,
            # Flag set to TRUE is the strip object should be given its share of any extra space
            #   within the packing box (the extra space is divided equally between all children of
            #   the box whose ->expandFlag is TRUE)
            expandFlag                  => FALSE,
            # Flag set to TRUE if any space given to the strip object by the 'expand' option is
            #   actually allocated within the strip object, FALSE if it is used as padding outside
            #   it (on both sides)
            fillFlag                    => FALSE,
            # Flag set to TRUE if the strip object should be packed into its window with a small
            #   gap between strip objects to either side; FALSE if not (can be set to FALSE if the
            #   the strip object's widgets are drawn in a way, such that a gap is not necessary,
            #   for example in the toolbar strip object)
            spacingFlag                 => TRUE,
            # Flag set to TRUE if only one instance of this strip object should be added to the
            #   parent window, set to FALSE if any number of instances can be added
            jealousyFlag                => TRUE,
            # Flag set to TRUE if this strip object can be added when $axmud::BLIND_MODE_FLAG is
            #   TRUE, FALSE if it can't be added (because it's not useful for visually-impaired
            #   users)
            blindFlag                   => FALSE,
            # Flag set to TRUE if the main container widget, stored in $self->packingBox, should be
            #   allowed to accept the focus, FALSE if not. The restriction is applied during the
            #   call to GA::Win::Internal->drawWidgets and ->addStripObj. Even if FALSE, widgets in
            #   the container widget can be set to accept the focus (e.g. the Gtk3::Entry in
            #   GA::Strip::MenuBar)
            allowFocusFlag              => FALSE,

            # Initialisation settings stored as a hash (see the comments above)
            initHash                    => \%modHash,
            # Reference to a function to call when some widget is used. This IV is set only when
            #   required by this type of strip object. It can be set by a call to
            #   $self->set_func() or by some setting in $self->initHash, which is applied in the
            #   call to $self->objEnable(). To obtain a reference to an OOP method, you can use the
            #   generic object function Games::Axmud->getMethodRef()
            funcRef                     => undef,
            # A value passed to ->funcRef when it is called which identifies this strip object and
            #   its widget(s). Can be any value, including 'undef'. It can be set by a call to
            #   $self->set_id() or by some setting in $self->initHash, which is applied in the call
            #   to $self->objEnable()
            funcID                      => undef,

            # The container widget for this strip object (usually a Gtk3::HBox or Gtk3::VBox). This
            #   widget is the one added to the window's main Gtk3::HBox or Gtk3::VBox
            packingBox                  => undef,       # Gtk3::HBox or Gtk3::VBox

            # Other IVs
            # ---------

            # Widgets
            hostLabel                   => undef,       # Gtk3::Label
            frame                       => undef,       # Gtk3::Frame
            canvas                      => undef,       # GooCanvas2::Canvas
            timeLabel                   => undef,       # Gtk3::Label

            # The GooCanvas2::CanvasRect objects that are drawn as blinkers - little blobs of colour
            #   which are lit up (briefly) when data is sent to and forth from the world
            # This version of Axmud implements the following blinker numbers:
            #   0   - blinker turned on when data is received from the world
            #   1   - blinker turned on when telnet option/protocol data (invisible to users) is
            #           received from the world
            #   2   - blinker turned on when a world command is sent
            # In each window, only the window object's ->visibleSession can light up blinkers; when
            #   the visible session changes, all blinkers are reset
            #
            # Hash of blinker objects (GA::Obj::Blinker), one for each blinker, in the form
            #   $blinkerHash{number} = blessed_reference_to_blinker_object
            blinkerHash                 => {},
            # Number of blinker objects created
            blinkerCount                => 3,
            # The portion of the GooCanvas2::Canvas required for each blinker, in pixels
            blinkerWidth                => 20,
            blinkerHeight               => 15,
        };

        # Bless the object into existence
        bless $self, $class;

        return $self;
    }

    ##################
    # Methods

    # Standard strip object functions



( run in 1.055 second using v1.01-cache-2.11-cpan-39bf76dae61 )