# 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::EditWin::XXX
# All 'edit' windows, inheriting from GA::Generic::ConfigWin

{ package Games::Axmud::EditWin::Buffer::Display;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Buffer::Display')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

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

        # Local variables
        my @emptyList;

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

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

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Line',
            ['Display buffer line'],
        );

        # Set up the rest of the tab
        $self->lineTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->tagsTab();
        $self->partsTab();
        $self->mxpTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub lineTab {

        # Line tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>Display buffer line</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Session',
            1, 3, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 1, 2, 8, 8);
        $entry->set_text($self->editObj->session->number);

        $self->addLabel($grid, 'Buffer location',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 2, 3, 8, 8);
        if ($self->editObj->parent eq 'session') {
            $entry2->set_text('Session');
        } else {
            $entry2->set_text('Combined');
        }
        $self->addLabel($grid, 'Line number',
            1, 3, 3, 4);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 3, 4, 8, 8);
        my $checkButton = $self->addCheckButton(
            $grid, 'Ends with newline character', 'newLineFlag', FALSE,
            1, 6, 4, 5);

        # Right column
        $self->addLabel($grid, 'Time received',
            7, 9, 1, 2);
        $self->addEntry($grid, 'time', FALSE,
            9, 12, 1, 2, 8, 8);
        $self->addLabel($grid, 'No. of packets',
            7, 9, 2, 3);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            9, 12, 2, 3);
        $entry3->set_text($self->editObj->ivNumber('partList'));
        my $checkButton2 = $self->addCheckButton($grid, 'Empty line', 'emptyFlag', FALSE,
            7, 12, 4, 5);

        # Bottom section
        # (The entry boxes are sensitive, but not editable, so the user can scroll along to see text
        #   that's too big to fit in the window)
        $self->addLabel($grid, '<u>Text received</u>',
            1, 12, 5, 6);
        $self->addLabel($grid, 'Original line (received from the world)',
            1, 12, 6, 7);
        my $entry4 = $self->addEntry($grid, undef, TRUE,
            1, 12, 7, 8);
        $entry4->set_editable(FALSE);
        $entry4->set_text($self->editObj->line);

        $self->addLabel($grid, 'Stripped line (escape sequences removed)',
            1, 12, 8, 9);
        my $entry5 = $self->addEntry($grid, undef, TRUE,
            1, 12, 9, 10);
        $entry5->set_editable(FALSE);
        $entry5->set_text($self->editObj->stripLine);

        $self->addLabel($grid, 'Modified line (after being modified by any matching interfaces)',
            1, 12, 10, 11);
        my $entry6 = $self->addEntry($grid, 'modLine', TRUE,
            1, 12, 11, 12);
        $entry6->set_editable(FALSE);
        $entry6->set_text($self->editObj->modLine);

        my $button = $self->addButton(
            $grid,
            'Update',
            'Update the stored data',
            undef,
            10, 12, 12, 13,
        );
        $button->signal_connect('clicked' => sub {

            # Update the widgets
            $entry3->set_text($self->editObj->ivNumber('partList'));
            $entry4->set_text($self->editObj->line);
            $entry5->set_text($self->editObj->stripLine);
            $entry6->set_text($self->editObj->modLine);

            $checkButton->set_active($self->editObj->newLineFlag);
            $checkButton2->set_active($self->editObj->emptyFlag);

            # If the received line of text doesn't end with a newline character, the next batch of
            #   text received will update this object's IVs. Otherwise, no further updates are
            #   possible, and the button should be desensitised
            if ($self->editObj->newLineFlag) {

                $button->set_sensitive(FALSE);
            }
        });

        # The button starts desensitised if a newline character has already been received
        if ($self->editObj->newLineFlag) {

            $button->set_sensitive(FALSE);
        }

        return 1;
    }

    sub tagsTab {

        # Tags tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @columnList2);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Tags',
            [$axmud::SCRIPT . ' colour/style tags'],
        );

        # Axmud colour/style tags
        $self->addLabel($grid, '<b>' . $axmud::SCRIPT . ' colour/style tags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of tags corresponding to escape sequences in the modified line</i>',
            1, 6, 1, 2);

        # Add a simple list
        @columnList = (
            'Tag', 'text',
            'Offsets (positions) at which the tag occurs', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 6, 2, 10);

        # Add an entry
        $self->addLabel($grid, 'Length of line (not including escape sequences)',
            1, 5, 10, 11);
        my $entry = $self->addEntry($grid, undef, FALSE,
            5, 6, 10, 11,
            8, 8);

        # Add textviews
        $self->addLabel(
            $grid,
            '<i>List of tags applied at end of previous line</i>',
            7, 12, 1, 2);
        my $textView = $self->addTextView($grid, undef, FALSE,
            7, 12, 2, 4,
            TRUE, TRUE, TRUE, FALSE);

        $self->addLabel(
            $grid,
            '<i>List of tags applied at beginning of this line</i>',
            7, 12, 4, 5);
        my $textView2 = $self->addTextView($grid, undef, FALSE,
            7, 12, 5, 7,
            TRUE, TRUE, TRUE, FALSE);

        $self->addLabel(
            $grid,
            '<i>List of tags applied at end of this line</i>',
            7, 12, 7, 8);
        my $textView3 = $self->addTextView($grid, undef, FALSE,
            7, 12, 8, 10,
            TRUE, TRUE, TRUE, FALSE);

        # Initialise the simple list and textview
        $self->tagsTab_refreshList(
            $slWidget,
            (scalar @columnList / 2),
            $textView,
            $textView2,
            $textView3,
            $entry,
        );

        # Add a button
        my $button = $self->addButton(
            $grid,
            'Refresh lists',
            'Refresh the lists of colour/style tags',
            undef,
            10, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->tagsTab_refreshList(
                $slWidget,
                (scalar @columnList / 2),
                $textView,
                $textView2,
                $textView3,
                $entry,
            );

            # If the received line of text doesn't end with a newline character, the next batch of
            #   text received will update this object's IVs. Otherwise, no further updates are
            #   possible, and the button should be desensitised
            if ($self->editObj->newLineFlag) {

                $button->set_sensitive(FALSE);
            }
        });

        # The button starts desensitised if a newline character has already been received
        if ($self->editObj->newLineFlag) {

            $button->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub tagsTab_refreshList {

        # Called by $self->tagsTab to refresh the GA::Obj::SimpleList and Gtk3::TextView
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $textView, $textView2, $textView3
        #               - The Gtk3::TextViews used
        #   $entry      - The Gtk3::Entry used
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $textView, $textView2, $textView3, $entry, $check) = @_;

        # Local variables
        my (
            @tagList, @dataList,
            %tagHash,
        );

        # Check for improper arguments
        if (
            ! defined $slWidget || ! defined $columns || ! defined $textView || ! defined $textView2
            || ! defined $textView3 || ! defined $entry || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->tagsTab_refreshList', @_);
        }

        # Import this line's hash of tags (for speed)
        %tagHash = $self->editObj->tagHash;

        # Sort them, so they appear in order of offset
        @tagList = sort {

            my $listRef = $tagHash{$a};
            my $listRef2 = $tagHash{$b};

            if ($$listRef[0] <= $$listRef2[0]) {
                -1;
            } elsif ($$listRef[0] >= $$listRef2[0]) {
                1;
            } else {
                $a cmp $b;
            }

        } (keys %tagHash);

        # Compile the simple list data
        foreach my $tag (@tagList) {

            my $listRef = $tagHash{$tag};
            push (@dataList, $tag, join(' ', @$listRef));
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        # Reset the textviews
        $textView->get_buffer->set_text(join("\n", $self->editObj->previousTagList));
        $textView2->get_buffer->set_text(join("\n", $self->editObj->initialTagList));
        $textView3->get_buffer->set_text(join("\n", $self->editObj->finalTagList));
        # Reset the entry box
        $entry->set_text(length($self->editObj->stripLine));

        return 1;
    }

    sub partsTab {

        # Parts tab
        #
        # 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 . '->partsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Parts',
            ['Line parts'],
        );

        # Line parts
        $self->addLabel($grid, '<b>Line parts</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $grid,
            '<i>List of line parts, each originating in a single data packet received from the'
            . ' world</i>',
            1, 12, 2, 4);

        my $textView = $self->addTextView($grid, 'partList', FALSE,
            1, 12, 4, 10,
            TRUE, TRUE, TRUE, FALSE);

        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of colour/style tags',
            undef,
            10, 12, 10, 12,
        );
        $button->signal_connect('clicked' => sub {

            $textView->buffer->set_text(join("\n", $self->editObj->partList));
        });

        # The button starts desensitised if a newline character has already been received
        if ($self->editObj->newLineFlag) {

            $button->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub mxpTab {

        # MXP tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_MXP',
            ['MXP tag properties'],
        );

        # MXP tag properties
        $self->addLabel($grid, '<b>MXP tag properties</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of tag properties (defined by custom elements) which terminate on this'
            . ' line</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Tag property', 'text',
            'Stored text', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->mxpTab_refreshList($slWidget, (scalar @columnList / 2));

        # Add a button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of MXP tag properties',
            undef,
            10, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            $self->mxpTab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # The button starts desensitised if a newline character has already been received
        if ($self->editObj->newLineFlag) {

            $button->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub mxpTab_refreshList {

        # Called by $self->mxpTab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - Number of columns in the simple list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @standardList, @dataList,
            %hash,
        );

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

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

        # Compile the simple list data. Display standard tag properties first, then any remaining
        #   ones
        @standardList = (
            'RoomName',
            'RoomDesc',
            'RoomExit',
            'RoomNum',
            'Prompt',
            'Set',
        );

        %hash = $self->getEditHash_hashIV('mxpFlagTextHash');

        foreach my $prop (@standardList) {

            if (exists $hash{$prop}) {

                push (@dataList, $prop, $hash{$prop});
            }

            delete ($hash{$prop});
        }

        foreach my $prop (sort {$a cmp $b} (keys %hash)) {

            push (@dataList, $prop, $hash{$prop});
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Buffer::Instruct;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Buffer::Instruct')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

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

        # Local variables
        my @emptyList;

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

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

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Item',
            ['Instruction buffer item'],
        );

        # Set up the rest of the tab
        $self->itemTab($grid);

#        # Set up the remaining tabs
#        $self->expandNotebook();       # (No more tabs to set up)

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub itemTab {

        # Item tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>Instruction buffer item</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Session',
            1, 3, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 1, 2, 8, 8);
        $entry->set_text($self->editObj->session->number);

        $self->addLabel($grid, 'Buffer location',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 2, 3, 8, 8);
        if ($self->editObj->parent eq 'session') {
            $entry2->set_text('Session');
        } else {
            $entry2->set_text('Combined');
        }
        $self->addLabel($grid, 'Item number',
            1, 3, 3, 4);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 3, 4);

        # Right column
        $self->addLabel($grid, 'Time processed',
            7, 9, 1, 2);
        $self->addEntry($grid, 'time', FALSE,
            9, 12, 1, 2);
        $self->addLabel($grid, 'Instruction type',
            7, 9, 2, 3);
        $self->addEntry($grid, 'type', FALSE,
            9, 12, 2, 3);

        # Bottom section
        # (The entry boxes are sensitive, but not editable, so the user can scroll along to see text
        #   that's too big to fit in the window)
        $self->addLabel($grid, 'Instruction',
            1, 12, 4, 5);
        my $entry3 = $self->addEntry($grid, 'instruct', TRUE,
            1, 12, 5, 6);
        $entry3->set_editable(FALSE);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Buffer::Cmd;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Buffer::Cmd')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

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

        # Local variables
        my @emptyList;

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

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

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Item',
            ['Command buffer item'],
        );

        # Set up the rest of the tab
        $self->itemTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->movesTab();

        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub itemTab {

        # Item tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>Command buffer item</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Session',
            1, 3, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 1, 2, 8, 8);
        $entry->set_text($self->editObj->session->number);

        $self->addLabel($grid, 'Buffer location',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 2, 3, 8, 8);
        if ($self->editObj->parent eq 'session') {
            $entry2->set_text('Session');
        } else {
            $entry2->set_text('Combined');
        }
        $self->addLabel($grid, 'Item number',
            1, 3, 3, 4);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 3, 4, 8, 8);

        # Right column
        $self->addLabel($grid, 'Time sent',
            7, 9, 1, 2);
        $self->addEntry($grid, 'time', FALSE,
            9, 12, 1, 2);

        # Bottom section
        # (The entry boxes are sensitive, but not editable, so the user can scroll along to see text
        #   that's too big to fit in the window)
        $self->addLabel($grid, 'Command',
            1, 12, 4, 5);
        my $entry3 = $self->addEntry($grid, 'cmd', TRUE,
            1, 12, 5, 6);
        $entry3->set_editable(FALSE);

        return 1;
    }

    sub movesTab {

        # Moves tab
        #
        # 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 . '->movesTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Moves',
            ['Move settings'],
        );

        # Move settings
        $self->addLabel($grid, '<b>Move settings</b>',
            0, 12, 0, 1);
        $self->addCheckButton($grid, 'Is a \'look\' command', 'lookFlag', FALSE,
            1, 6, 1, 2);
        $self->addCheckButton($grid, 'Is a \'glance\' command', 'glanceFlag', FALSE,
            1, 6, 2, 3);
        $self->addCheckButton($grid, 'Is a movement command', 'moveFlag', FALSE,
            1, 6, 3, 4);
        $self->addCheckButton($grid, 'Is a redirect mode command', 'redirectFlag', FALSE,
            1, 6, 5, 6);
        $self->addCheckButton($grid, 'Is an assisted move', 'assistedFlag', FALSE,
            1, 6, 6, 7);
        $self->addCheckButton($grid, 'Is a teleport', 'teleportFlag', FALSE,
            1, 6, 9, 10);

        # Right column
        $self->addLabel($grid, 'Move direction',
            7, 9, 3, 4);
        $self->addEntry($grid, 'moveDir', FALSE,
            9, 12, 3, 4);
        $self->addLabel($grid, 'Move verb',
            7, 9, 4, 5);
        $self->addEntry($grid, 'moveVerb', FALSE,
            9, 12, 4, 5);
        $self->addLabel($grid, 'Substituted command',
            7, 9, 5, 6);
        $self->addEntry($grid, 'redirectCmd', FALSE,
            9, 12, 5, 6);
        $self->addLabel($grid, 'Standard primary direction',
            7, 9, 6, 7);
        $self->addEntry($grid, 'assistedPrimary', FALSE,
            9, 12, 6, 7);

        $self->addLabel($grid, 'Assisted command list',
            7, 9, 7, 8);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            9, 12, 7, 8);
        $entry3->set_text(join($axmud::CLIENT->cmdSep, $self->editObj->assistedList));

        $self->addLabel($grid, 'Exit number',
            7, 9, 8, 9);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            9, 12, 8, 9);
        if ($self->editObj->assistedExitObj) {

            $entry4->set_text($self->editObj->assistedExitObj->number);
        }

        $self->addLabel($grid, 'Destination room (if known)',
            7, 9, 9, 10);
        $self->addEntry($grid, 'teleportDestRoom', FALSE,
            9, 12, 9, 10);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Cage::Cmd;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Cage Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Cage::Cmd')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which type of cage is being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->commandsTab();
        $self->wordsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub commandsTab {

        # Commands tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @cageList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Commands',
            ['Interpolated command strings'],
        );

        # Interpolated command strings
        $self->addLabel($grid, '<b>Interpolated command strings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of command strings which can be interpolated in a different way for each'
            . ' world</i>',
            1, 12, 1, 2);

        # Add a simple list. Column three (onwards) contains all inferior cages. Each column (except
        #   the first two) has a title containing the cage's associated profile
        @cageList = $self->compileCages();
        # Use different text in the column for the first item in @cageList
        shift @cageList;

        @columnList = (
            'Command', 'text',
            'Replacement', 'text',
        );

        foreach my $cage (@cageList) {

            push (@columnList,
                '[' . $cage->profCategory . ': ' . $cage->profName . ']',
                'text',
            );
        }

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList($slWidget, (scalar @columnList / 2), 'cmdHash');

        # Add entry boxes and edit buttons
        $self->addLabel($grid, 'For the selected command:',
            1, 4, 10, 12);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 6, 10, 11);

        $self->addCageButtons($grid, $slWidget, $entry, 'cmdHash', (scalar @columnList / 2));

        # Tab complete
        return 1;
    }

    sub wordsTab {

        # Words tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @cageList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Words',
            ['Interpolated word strings'],
        );

        # Interpolated word strings
        $self->addLabel($grid, '<b>Interpolated word strings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of word strings which are automatically interpolated for each world</i>',
            1, 12, 1, 2);

        # Add a simple list. Column three (onwards) contains all inferior cages. Each column (except
        #   the first two) has a title containing the cage's associated profile
        @cageList = $self->compileCages();
        # Use different text in the column for the first item in @cageList
        shift @cageList;

        @columnList = (
            'Word', 'text',
            'Replacement', 'text',
        );

        foreach my $cage (@cageList) {

            push (@columnList,
                '[' . $cage->profCategory . ': ' . $cage->profName . ']',
                'text',
            );
        }

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList($slWidget, (scalar @columnList / 2), 'wordHash');

        # Add entry boxes and edit buttons
        $self->addLabel($grid, 'For the selected word:',
            1, 4, 10, 12);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 6, 10, 11);

        $self->addCageButtons($grid, $slWidget, $entry, 'wordHash', (scalar @columnList / 2));

        # Tab complete
        return 1;
    }

    sub addCageButtons {

        # Called by $self->commandsTab and $self->wordsTab to create the editing buttons
        #   beneath the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $grid       - The current Gtk3::Grid displayed in the notebook
        #   $slWidget   - The GA::Obj::SimpleList
        #   $entry      - A Gtk3::Entry needed by one of the buttons
        #   $iv         - The IV being edited in this tab - 'cmdHash' or 'wordHash'
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $slWidget, $entry, $iv, $columns, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $grid || ! defined $slWidget || ! defined $entry || ! defined $iv
            || ! defined $columns || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->addCageButtons', @_);
        }

        # Add entry boxes and edit buttons
        my $button = $self->addButton(
            $grid,
            'Set new value',
            'Set the current value of the selected command',
            undef,
            6, 8, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            my ($cmd, $value);

            ($cmd) = $self->getSimpleListData($slWidget, 0);
            if (defined $cmd && $self->checkEntryIcon($entry)) {

                # Set the new value of the selected command
                $value = $entry->get_text();
                # Save the key-value pair in the hash IV
                $self->modifyEditHash_hashIV($iv, $cmd, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList($slWidget, $columns, $iv);
                $self->resetEntryBoxes($entry);
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Don\'t use',
            'Don\'t use this command string (set its value to \'undef\'',
            undef,
            8, 10, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            my ($cmd) = $self->getSimpleListData($slWidget, 0);
            if (defined $cmd) {

                # Save the key-value pair in the hash IV
                $self->modifyEditHash_hashIV($iv, $cmd, undef);

                # Refresh the simple list and reset entry boxes
                $self->refreshList($slWidget, $columns, $iv);
                $self->resetEntryBoxes($entry);
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Clear all',
            'Clear all command strings from this cage',
            undef,
            10, 12, 10, 11,
        );
        $button3->signal_connect('clicked' => sub {

            my %ivHash;

            foreach my $cmd ($self->editObj->ivKeys($iv)) {

                $ivHash{$cmd} = undef;
            }

            $self->ivAdd('editHash', $iv, \%ivHash);

            # Refresh the simple list and reset entry boxes
            $self->refreshList($slWidget, $columns, $iv);
            $self->resetEntryBoxes($entry);
        });

        return 1;
    }

    sub refreshList {

        # Called by $self->commandsTab and $self->wordsTab to reset the
        #   GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $iv         - The IV being edited in this tab - 'cmdHash' or 'wordHash'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $iv, $check) = @_;

        # Local variables
        my (
            @sortedList, @cageList, @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $iv || defined $check) {

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

        # Get a sorted list of commands from the cage
        @sortedList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys($iv));
        # Get a list of columns in the simple list, which depends on how many cages there are
        @cageList = $self->compileCages();
        # (We already know the first cage)
        shift @cageList;

        # Import the IV
        %ivHash = $self->getEditHash_hashIV($iv);

        # Compile the simple list data
        OUTER: foreach my $cmd (@sortedList) {

            # First two columns, showing the command and its interpolated form...
            push (@dataList, $cmd, $ivHash{$cmd});

            # Remaining columns
            INNER: foreach my $cage (@cageList) {

                if ($cage->ivExists($iv, $cmd)) {

                    # By supplying only two arguments, we instruct ->ivShow to give us the value
                    #   stored in the cage, and not to consult its inferiors (if no value is stored
                    #   there)
                    push (@dataList, $cage->ivShow($iv, $cmd));

                } else {

                    # $cage doesn't store an interpolated form of the command
                    push (@dataList, undef);
                }
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Cage::Trigger;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::InterfaceCage Games::Axmud::EditWin::Generic::Cage
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Cage::Trigger')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub interfacesTab {

        # Interfaces tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $interfaceModelObj;

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

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

        # Unusual step - we need to remember both simple lists displayed in these tabs, so create a
        #   few extra IVs
        $self->ivPoke('slWidget1', undef);
        $self->ivPoke('slWidget2', undef);

        # We also need to store a few strings for this kind of interface (triggers, in this case)
        $self->ivPoke('singular', 'trigger');
        $self->ivPoke('plural', 'triggers');

        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', 'trigger');
        $self->ivPoke('interfaceModelObj', $interfaceModelObj);
        $self->ivPoke('stimulus', $interfaceModelObj->stimulusName);    # i.e. 'pattern'
        $self->ivPoke('response', $interfaceModelObj->responseName);    # i.e. 'instruction'

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Triggers');

        # Add tabs to the inner notebook
        $self->interfaces1Tab($innerNotebook);
        $self->interfaces2Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Cage::Alias;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::InterfaceCage Games::Axmud::EditWin::Generic::Cage
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Cage::Alias')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub interfacesTab {

        # Interfaces tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $interfaceModelObj;

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

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

        # Unusual step - we need to remember both simple lists displayed in these tabs, so create a
        #   few extra IVs
        $self->ivPoke('slWidget1', undef);
        $self->ivPoke('slWidget2', undef);

        # We also need to store a few strings for this kind of interface (aliases, in this case)
        $self->ivPoke('singular', 'alias');
        $self->ivPoke('plural', 'aliases');

        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', 'alias');
        $self->ivPoke('interfaceModelObj', $interfaceModelObj);
        $self->ivPoke('stimulus', $interfaceModelObj->stimulusName);    # i.e. 'pattern'
        $self->ivPoke('response', $interfaceModelObj->responseName);    # i.e. 'substitution'

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Aliases');

        # Add tabs to the inner notebook
        $self->interfaces1Tab($innerNotebook);
        $self->interfaces2Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Cage::Macro;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::InterfaceCage Games::Axmud::EditWin::Generic::Cage
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Cage::Macro')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub interfacesTab {

        # Interfaces tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $interfaceModelObj;

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

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

        # Unusual step - we need to remember both simple lists displayed in these tabs, so create a
        #   few extra IVs
        $self->ivPoke('slWidget1', undef);
        $self->ivPoke('slWidget2', undef);

        # We also need to store a few strings for this kind of interface (macros, in this case)
        $self->ivPoke('singular', 'macro');
        $self->ivPoke('plural', 'macros');

        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', 'macro');
        $self->ivPoke('interfaceModelObj', $interfaceModelObj);
        $self->ivPoke('stimulus', $interfaceModelObj->stimulusName);    # i.e. 'keycode'
        $self->ivPoke('response', $interfaceModelObj->responseName);    # i.e. 'instruction'

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Macros');

        # Add tabs to the inner notebook
        $self->interfaces1Tab($innerNotebook);
        $self->interfaces2Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Cage::Timer;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::InterfaceCage Games::Axmud::EditWin::Generic::Cage
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Cage::Timer')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub interfacesTab {

        # Interfaces tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $interfaceModelObj;

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

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

        # Unusual step - we need to remember both simple lists displayed in these tabs, so create a
        #   few extra IVs
        $self->ivPoke('slWidget1', undef);
        $self->ivPoke('slWidget2', undef);

        # We also need to store a few strings for this kind of interface (timers, in this case)
        $self->ivPoke('singular', 'timer');
        $self->ivPoke('plural', 'timers');

        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', 'timer');
        $self->ivPoke('interfaceModelObj', $interfaceModelObj);
        $self->ivPoke('stimulus', $interfaceModelObj->stimulusName);    # i.e. 'interval'
        $self->ivPoke('response', $interfaceModelObj->responseName);    # i.e. 'instruction'

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'T_imers');

        # Add tabs to the inner notebook
        $self->interfaces1Tab($innerNotebook);
        $self->interfaces2Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Cage::Hook;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::InterfaceCage Games::Axmud::EditWin::Generic::Cage
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Cage::Hook')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub interfacesTab {

        # Interfaces tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $interfaceModelObj;

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

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

        # Unusual step - we need to remember both simple lists displayed in these tabs, so create a
        #   few extra IVs
        $self->ivPoke('slWidget1', undef);
        $self->ivPoke('slWidget2', undef);

        # We also need to store a few strings for this kind of interface (hooks, in this case)
        $self->ivPoke('singular', 'hook');
        $self->ivPoke('plural', 'hooks');

        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', 'hook');
        $self->ivPoke('interfaceModelObj', $interfaceModelObj);
        $self->ivPoke('stimulus', $interfaceModelObj->stimulusName);    # i.e. 'hook_event'
        $self->ivPoke('response', $interfaceModelObj->responseName);    # i.e. 'instruction'

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Hooks');

        # Add tabs to the inner notebook
        $self->interfaces1Tab($innerNotebook);
        $self->interfaces2Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Cage::Route;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::InterfaceCage Games::Axmud::EditWin::Generic::Cage
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Cage::Route')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Cage settings'],
        );

        # Route cage 'edit' windows have some non-standard IVs, used to allow one tab to affect the
        #   contents of others
        # The values for each IV are set in $self->routes1Tab and ->routes2Tab
        $self->{slWidget1} = undef;
        $self->{slWidget2} = undef;

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which type of cage is being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->routesTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub routesTab {

        # Routes tab
        #
        # 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 . '->routesTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Routes');

        # Add tabs to the inner notebook
        $self->routes1Tab($innerNotebook);
        $self->routes2Tab($innerNotebook);

        return 1;
    }

    sub routes1Tab {

        # Routes1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @cageList, @typeList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Route list with attributes'],
        );

        # Routes list
        $self->addLabel($grid, '<b>Routes list</b>',
            0, 13, 0, 1);
        $self->addLabel($grid,
            '<i>List of routes belonging to this cage (showing route attributes)</i>',
            1, 13, 1, 2);

        # Add a simple list
        @columnList = (
            'Stored as', 'text',
            'Type', 'text',
            'Hop', 'bool',
            'Steps', 'int',
            'Start tag', 'text',
            'Stop tag', 'text',
            'Circuit', 'text',
            'Route', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 13, 2, 8);

        # Unusual step - save the list reference in an IV
        $self->ivPoke('slWidget2', $slWidget);

        # Initialise the list
        $self->routes1Tab_refreshList();

        # Add some buttons and entry boxes
        $self->routesTab_addEditButtons($grid, $slWidget, (scalar @columnList / 2));

        # Tab complete
        return 1;
    }

    sub routes1Tab_refreshList {

        # Called by $self->routes1Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @sortedList, @dataList,
            %routeHash,
        );

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

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

        # Import the IV
        %routeHash = $self->editObj->routeHash;

        # Get a sorted list of route objects from the cage
        @sortedList = sort {lc($a) cmp lc($b)} (keys %routeHash);

        # Compile the simple list data
        foreach my $routeKey (@sortedList) {

            my $routeObj = $routeHash{$routeKey};

            push (@dataList,
                $routeKey,
                $routeObj->routeType,
                $routeObj->hopFlag,
                $routeObj->stepCount,
                $routeObj->startRoom,
                $routeObj->stopRoom,
                $routeObj->circuitName,
                $routeObj->route,
            );
        }

        # Reset the simple list (page 2 has four columns)
        $self->resetListData($self->{slWidget2}, [@dataList], 8);

        return 1;
    }

    sub routes2Tab {

        # Routes2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @cageList, @typeList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Route list with lower-priority cages'],
        );

        # Routes list
        $self->addLabel($grid, '<b>Routes list</b>',
            0, 13, 0, 1);
        $self->addLabel($grid,
            '<i>List of routes belonging to this cage (compared with lower-priority cages)</i>',
            1, 13, 1, 2);

        # Add a simple list. The number of columns is equal to the number of cages between this one
        #   and the least superior cage. (If this cage has no inferior, there's only one column)
        # Each column (except the first one) has a title containing the cage's associated profile
        @cageList = $self->compileCages();

        # Use different text in the column for the first item in @cageList
        @columnList = ('Stored as', 'text');
        shift @cageList;

        foreach my $cage (@cageList) {

            push (@columnList,
                '[' . $cage->profCategory . ': ' . $cage->profName . ']',
                'text',
            );
        }

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 13, 2, 8);

        # Unusual step - save the list reference in an IV
        $self->ivPoke('slWidget1', $slWidget);

        # Initialise the list
        $self->routes2Tab_refreshList();

        # Add some buttons and entry boxes
        $self->routesTab_addEditButtons($grid, $slWidget, (scalar @columnList / 2));

        # Tab complete
        return 1;
    }

    sub routes2Tab_refreshList {

        # Called by $self->routes2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@sortedList, @cageList, @dataList);

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

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

        # Get a sorted list of route keys from the cage
        @sortedList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('routeHash'));
        # Get a list of columns in the simple list, which depends on how many cages there are
        @cageList = $self->compileCages();

        # Compile the simple list data
        OUTER: foreach my $routeKey (@sortedList) {

            INNER: foreach my $cageObj (@cageList) {

                if ($cageObj->ivExists('routeHash', $routeKey)) {

                    # Don't display the route object's blessed reference - just display the route in
                    #   the form used by keys in the cage's ->routeHash
                    push (@dataList, $routeKey);

                } else {

                    # An route called $routeKey isn't stored in $cageObj
                    push (@dataList, undef);
                }
            }
        }

        # Reset the simple list
        $self->resetListData($self->slWidget1, [@dataList], scalar @cageList);

        return 1;
    }

    sub routesTab_addEditButtons {

        # Called by $self->routes1Tab and $self->routes2Tab to create the editing buttons beneath
        #   the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $grid       - The current Gtk3::Grid displayed in the notebook
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $slWidget, $columns, $check) = @_;

        # Local variables
        my @typeList;

        # Check for improper arguments
        if (! defined $grid || ! defined $slWidget || ! defined $columns || defined $check) {

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

        # Add comboboxes, check buttons and entries
        $self->addLabel($grid, 'Route type',
            1, 4, 8, 9);
        @typeList = ('road', 'quick', 'circuit');
        my $combo = $self->addComboBox($grid, undef, \@typeList, '',
            TRUE,               # No 'undef' value used
            4, 5, 8, 9);

        my $checkButton = $self->addCheckButton($grid, 'Hoppable', undef, TRUE,
            5, 7, 8, 9);
        # New routes should be hoppable by default
        $checkButton->set_active(TRUE);

        $self->addLabel($grid, 'Start room tag',
            7, 10, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            10, 13, 8, 9);

        $self->addLabel($grid, 'Stop room tag',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            4, 7, 9, 10);

        $self->addLabel($grid, 'Circuit name',
            7, 10, 9, 10);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            10, 13, 9, 10);

        $self->addLabel($grid, 'Route',
            1, 4, 10, 11);
        my $entry4 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 13, 10, 11);

        # Add editing buttons
        my $button = $self->addButton(
            $grid,
            'Add route',
            'Add a new pre-defined route',
            undef,
            3, 5, 11, 12,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($routeType, $hopFlag, $startTag, $stopTag, $circuitName, $route, $cmd);

            $routeType = $combo->get_active_text();
            $hopFlag = $checkButton->get_active();
            $startTag = $entry->get_text();
            $stopTag = $entry2->get_text();
            $circuitName = $entry3->get_text();
            $route = $entry4->get_text();

            # Prepare the client command
            if ($routeType eq 'road') {

                if ($self->checkEntryIcon($entry, $entry2, $entry4)) {

                    $cmd = 'addroute ' . $startTag . ' ' . $stopTag . ' <' . $route . '> -o';
                }

            } elsif ($routeType eq 'quick') {

                if ($self->checkEntryIcon($entry, $entry2, $entry4)) {

                    $cmd = 'addroute ' . $startTag . ' ' . $stopTag . ' <' . $route . '> -q';
                }

            } else {

                if ($self->checkEntryIcon($entry, $entry3, $entry4)) {

                    $cmd = 'addroute ' . $startTag . ' ' . $circuitName . ' <' . $route . '> -t';
                }
            }

            if ($cmd) {

                $cmd .= ' -d ' . $self->editObj->profName;

                if (! $hopFlag) {

                    $cmd .= ' -h';
                }

                # Send the command
                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);

                # Refresh the simple lists in both tabs
                $self->routes1Tab_refreshList();
                $self->routes2Tab_refreshList();
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Reverse route',
            'Set up a route in the reverse direction',
            undef,
            1, 3, 11, 12,
        );
        $button2->signal_connect('clicked' => sub {

            my ($startTag, $stopTag, $sequence, $reverse);

            # The user must have entered a start tag, stop tag and route
            if ($self->checkEntryIcon($entry, $entry2, $entry4)) {

                $startTag = $entry->get_text();
                $stopTag = $entry2->get_text();
                $sequence = $entry4->get_text();

                # See if we can reverse the sequence of world commands in $sequence
                ($reverse) = $self->session->worldModelObj->reversePath(
                    $self->session,
                    'no_abbrev',        # Don't abbreviate, use first opposite direction
                    $sequence,
                );

                if ($reverse) {

                    # Change the contents of the entry boxes; don't create a new route
                    $entry->set_text($stopTag);
                    $entry2->set_text($startTag);
                    $entry4->set_text($reverse);

                } else {

                    $self->showMessageDialogue(
                        'Reverse route',
                        'Error',
                        'Couldn\'t reverse the list of world commands',
                        'ok',
                        $self->winWidget,
                    );
                }

                # Refresh the simple lists in both tabs
                $self->routes1Tab_refreshList();
                $self->routes2Tab_refreshList();
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Edit...',
            'Edit the selected pre-defined route',
            undef,
            5, 7, 11, 12,
        );
        $button3->signal_connect('clicked' => sub {

            my ($routeName, $routeObj, $string, $childWinObj);

            ($routeName) = $self->getSimpleListData($slWidget, 0);
            if ($routeName) {

                $routeObj = $self->editObj->ivShow('routeHash', $routeName);

                if (defined $routeObj) {

                    $string = 'Edit ' . $routeObj->routeType . ' route';
                    if ($routeObj->routeType eq 'circuit') {
                        $string .= ' \'' . $routeObj->circuitName . '\'';
                    } else {
                        $string .= ' (' . $routeObj->startRoom . ' > ' . $routeObj->stopRoom . ')';
                    }

                    # Open an 'edit' window for the route object
                    $childWinObj = $self->createFreeWin(
                        'Games::Axmud::EditWin::Route',
                        $self,
                        $self->session,
                        $string,
                        $routeObj,
                        FALSE,                  # Not temporary
                    );
                }

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'routes1Tab_refreshList',
                        [],             # Function requires no arguments
                    );

                    $self->add_childDestroy(
                        $childWinObj,
                        'routes2Tab_refreshList',
                        [],             # Function requires no arguments
                    );
                }
            }
        });

        my $button4 = $self->addButton(
            $grid,
            'Delete',
            'Delete the selected pre-defined route',
            undef,
            7, 9, 11, 12,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my ($routeName, $routeObj, $cmd);

            ($routeName) = $self->getSimpleListData($slWidget, 0);
            $routeObj = $self->editObj->ivShow('routeHash', $routeName);

            if (defined $routeObj) {

                # Prepare the client command
                if ($routeObj->routeType eq 'road') {

                    $cmd = 'deleteroute ' . $routeObj->startRoom  . ' ' . $routeObj->stopRoom
                            . ' -o';

                } elsif ($routeObj->routeType eq 'quick') {

                    $cmd = 'deleteroute ' . $routeObj->startRoom  . ' ' . $routeObj->stopRoom
                            . ' -q';

                } else {

                    $cmd = 'deleteroute ' . $routeObj->startRoom  . ' ' . $routeObj->circuitName
                            . ' -t';
                }

                if ($cmd) {

                    $cmd .= ' -d ' . $self->editObj->profName;

                    # Delete the route
                    $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);

                    # Refresh the simple lists in both tabs
                    $self->routes1Tab_refreshList();
                    $self->routes2Tab_refreshList();
                }
            }
        });

        my $button5 = $self->addButton(
            $grid,
            'Dump routes',
            'Display this list of routes in the \'main\' window',
            undef,
            9, 11, 11, 12,
        );
        $button5->signal_connect('clicked' => sub {

            my $profName = $self->editObj->profName;
            if ($profName) {

                # Display the list of routes
                $self->session->pseudoCmd('listroute -d ' . $profName, $self->pseudoCmdMode);
            }
        });

        my $button6 = $self->addButton(
            $grid,
            'Refresh list',
            'Refreshes this list of routes',
            undef,
            11, 13, 11, 12,
        );
        $button6->signal_connect('clicked' => sub {

            # Refresh the simple lists in both tabs
            $self->routes1Tab_refreshList();
            $self->routes2Tab_refreshList();
        });

        # Depending on the combo box setting, sensitise/desensitise the boxes, as needed
        $combo->signal_connect('changed' => sub {

            my $text = $combo->get_active_text();

            if ($text eq 'road' || $text eq 'quick') {

                $checkButton->set_sensitive(TRUE);  # Hoppable
                $entry->set_sensitive(TRUE);        # Start room
                $entry2->set_sensitive(TRUE);       # Stop room
                $entry3->set_text('');              # Circuit name
                $entry3->set_sensitive(FALSE);

            } else {

                $checkButton->set_sensitive(FALSE); # Hoppable
                $entry->set_sensitive(TRUE);        # Start room
                $entry2->set_text('');              # Stop room
                $entry2->set_sensitive(FALSE);
                $entry3->set_sensitive(TRUE);       # Circuit name
            }
        });

        # We start on the 'road' setting, so the circuit entry box is insensitive
        $entry3->set_sensitive(FALSE);              # Circuit name

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ChatContact;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::ChatContact')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Chat contact settings'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>Chat contact settings</b>',
            0, 6, 0, 1);

        $self->addLabel($grid, 'Unique name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);

        $self->addLabel($grid, 'Protocol',
            1, 3, 2, 3);
        @comboList = ('MudMaster', 'zChat');
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 2, 3);
        $comboBox->set_active($self->editObj->protocol);    # IV is set to either 0 or 1
        $comboBox->signal_connect('changed' => sub {

            my $text = $comboBox->get_active_text();
            if ($text eq 'MudMaster') {
                $self->ivAdd('editHash', 'protocol', 0);
            } else {
                $self->ivAdd('editHash', 'protocol', 1);
            }
        });

        $self->addLabel($grid, 'IP address',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'ip', 'string', 1, undef,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Port',
            1, 3, 4, 5);
        $self->addEntryWithIcon($grid, 'port', 'int', 0, 65535,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Email',
            1, 3, 5, 6);
        $self->addEntry($grid, 'email', FALSE,
            3, 12, 5, 6);

        # Right column
        $self->addImage($grid, undef, $self->editObj->lastIcon,
            FALSE,          # Don't use a scrolled window
            128, 128,
            7, 12, 1, 5);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ColourScheme;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::ColourScheme')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Colour scheme settings'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->colourTab();
        $self->overrideTab();
        $self->textTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Update all 'internal' windows using this colour scheme
            $self->session->pseudoCmd(
                'updatecolourscheme ' . $self->editObj->name,
                $self->pseudoCmdMode,
            );
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Colour scheme settings
        $self->addLabel($grid, '<b>Colour scheme settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', 0,
            3, 6, 1, 2);
        my $checkButton = $self->addCheckButton($grid, 'Standard colour scheme', undef, FALSE,
            7, 12, 1, 2);
        if (
            $axmud::CLIENT->ivExists('constGridWinTypeHash', $self->editObj->name)
            || $axmud::CLIENT->ivExists('constFreeWinTypeHash', $self->editObj->name)
        ) {
            $checkButton->set_active(TRUE);
        }

        # Tab complete
        return 1;
    }

    sub colourTab {

        # Colour tab
        #
        # 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 . '->colourTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Colours',
            ['Colour settings'],
        );

        # Colour settings
        $self->addLabel($grid, '<b>Colour settings</b>',
            0, 12, 0, 1);

        my $hiddenButton = $self->colourTab_addRow($grid, 'textColour', 'Text', 1);
        my $hiddenButton2 = $self->colourTab_addRow($grid, 'underlayColour', 'Underlay', 3);
        my $hiddenButton3 = $self->colourTab_addRow(
            $grid,
            'backgroundColour',
            'Background',
            5,
        );

        my $button = $self->addButton($grid,
            'Set underlay to match background',
            'Update the underlay colour',
            undef,
            1, 12, 7, 8);
        $button->signal_connect('clicked' => sub {

            my $background = $self->getEditHash_scalarIV('backgroundColour');

            # Update IVs
            $self->ivAdd(
                'editHash',
                'underlayColour',
                $axmud::CLIENT->swapColours($background),
            );

            # Click the hidden button to redraw the GooCanvas2::Canvas/Gtk3::Entry objects
            $hiddenButton2->clicked();
        });

        my $button2 = $self->addButton($grid,
            'Swap text and background colours',
            'Update the text and background colours',
            undef,
            1, 12, 8, 9);
        $button2->signal_connect('clicked' => sub {

            my ($text, $underlay, $background);

            $text = $self->getEditHash_scalarIV('textColour');
            $underlay = $axmud::CLIENT->swapColours($self->getEditHash_scalarIV('underlayColour'));
            $background = $self->getEditHash_scalarIV('backgroundColour');

            # Update IVs. If the background and underlay are the same colour, update that, too
            $self->ivAdd('editHash', 'textColour', $background);
            $self->ivAdd('editHash', 'backgroundColour', $text);
            if ($underlay eq $background) {

                $self->ivAdd('editHash', 'underlayColour', $axmud::CLIENT->swapColours($text));
            }

            # Click the hidden buttons to redraw the GooCanvas2::Canvas/Gtk3::Entry objects
            $hiddenButton->clicked();
            $hiddenButton2->clicked();
            $hiddenButton3->clicked();
        });

        # Tab complete
        return 1;
    }

    sub colourTab_addRow {

        # Called by $self->nameTab
        # Adds a single row of labels, entry boxes and buttons to allow configuration of a single
        #   colour setting
        #
        # Expected arguments
        #   $grid       - The Gtk3::Grid for this tab
        #   $iv         - The IV to use, e.g. 'textColour'
        #   $text       - The label to use, e.g. 'Text colour'
        #   $row        - The number of the row in the Gtk3::Grid displayed in this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns a Gtk3::Button which isn't visible in the tab, but which can be
        #       'clicked' by the calling function, and thereby change the displayed colour

        my ($self, $grid, $iv, $text, $row, $check) = @_;

        # Local variables
        my (
            $title, $colour, $rgbColour, $constIV, $default, $rgbDefault,
            @tagList,
        );

        # Check for improper arguments
        if (
            ! defined $grid || ! defined $iv || ! defined $text || ! defined $row
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->colourTab_addRow', @_);
        }

        # Initialise vars
        $title = 'Or select tag:';
        $colour = $self->editObj->$iv;
        $rgbColour = $axmud::CLIENT->returnRGBColour($colour);

        # (e.g. convert GA::Obj::ColourScheme->textColour to GA::Client->constTextColour)
        $constIV = 'const' . ucfirst($iv);
        $default = $axmud::CLIENT->$constIV;
        $rgbDefault = $axmud::CLIENT->returnRGBColour($default);

        # $rgbColour and $rgbDefault are used to fill the boxes with colour, so convert an Axmud
        #   underlay tag to a non-underlay tag
        $rgbColour =~ s/^[Uu]//;
        $rgbDefault =~ s/^[Uu]//;

        # Prepare a list of items for a combo
        if ($iv eq 'underlayColour') {

            foreach my $tag ($axmud::CLIENT->constColourTagList) {

                push (@tagList, 'ul_' . $tag);
            }

            foreach my $tag ($axmud::CLIENT->constBoldColourTagHash) {

                push (@tagList, 'UL_' . $tag);
            }

        } else {

            @tagList = ($axmud::CLIENT->constColourTagList, $axmud::CLIENT->constBoldColourTagList);
        }

        # Colour 1
        $self->addLabel($grid, $text,
            1, 3, $row, ($row + 1));

        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($grid, $rgbColour, undef,
            3, 4, $row, ($row + 1));

        my $entry = $self->addEntry($grid, undef, FALSE,
            4, 6, $row, ($row + 1), 16, 16);
        $entry->set_text($colour);

        my $button = $self->addButton($grid, 'Change', 'Change this colour', undef,
            6, 7, $row, ($row + 1));
        $button->signal_connect('clicked' => sub {

            # Prompt the user to select a new colour, using the existing colour as an initial value
            my $rgbModify = $self->showColourSelectionDialogue('Set ' . lc($text), $rgbColour);

            if ($rgbModify) {

                $rgbColour = $rgbModify;
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $rgbColour);

                # Convert a text colour tag to an underlay colour tag, if it's an underlay IV
                if ($iv eq 'underlayColour') {

                    $rgbModify = $axmud::CLIENT->swapColours($rgbModify);
                }

                $entry->set_text($rgbModify);

                # Update IVs
                $self->ivAdd('editHash', $iv, $rgbModify);
            }
        });

        # Colour 2
        my $combo = $self->addComboBox($grid, undef, \@tagList, $title,
            TRUE,           # no 'undef'
            4, 6, ($row + 1), ($row + 2));

        my $button2 = $self->addButton($grid, 'Use', 'Use this colour tag', undef,
            6, 7, ($row + 1), ($row + 2));
        $button2->signal_connect('clicked' => sub {

            my $text = $combo->get_active_text();
            if ($text && $text ne $title) {

                $canvasObj = $self->fillSimpleCanvas(
                    $canvas,
                    $canvasObj,
                    $axmud::CLIENT->returnRGBColour($text),
                );

                $entry->set_text($text);

                # Update IVs
                $self->ivAdd('editHash', $iv, $text);
            }
        });

        # Default colour
        $self->addSimpleCanvas($grid, $rgbDefault, undef,
            8, 9, $row, ($row + 1));

        my $entry2 = $self->addEntry($grid, undef, FALSE,
            9, 11, $row, ($row + 1), 16, 16);
        $entry2->set_text($default);

        my $button3 = $self->addButton($grid, 'Use default', 'Use the default colour', undef,
            11, 12, $row, ($row + 1));
        $button3->signal_connect('clicked' => sub {

            $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $rgbDefault);
            $entry->set_text($default);

            # Update IVs
            $self->ivAdd('editHash', $iv, $default);
        });

        # Create a Gtk3::Button which isn't visible in the tab, but which can be 'clicked' by the
        #   calling function, and thereby change the displayed colour
        my $hiddenButton = Gtk3::Button->new('Hidden');
        $hiddenButton->signal_connect('clicked' => sub {

            $rgbColour = $self->getEditHash_scalarIV($iv);
            $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $rgbColour);

            $entry->set_text($rgbColour);

            # Update IVs
            $self->ivAdd('editHash', $iv, $rgbColour);
        });

        return $hiddenButton;
    }

    sub overrideTab {

        # Override tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, 'Colour _overrides');

        # Colour overrides
        $self->addLabel($grid, '<b>Colour overrides</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of ' . $axmud::SCRIPT . ' colour tags which should be ignored or replaced when'
            . ' text is displayed using this colour scheme</i>',
            1, 12, 1, 2);
        my $checkButton = $self->addCheckButton(
            $grid,
            'Ignore all colour tags except the normal text and background colours',
            'overrideAllFlag',
            TRUE,
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Original colour', 'text',
            'Replacement colour', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 9);

        # Initialise the simple list
        $self->overrideTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets
        $self->addLabel($grid,
            'Colour tag  - Standard (e.g. <i>red, BLUE</i>), Xterm (e.g. <i>x128</i>)'
            . ' or RGB (e.g. <i>#ABCDEF</i>)',
            1, 9, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, \&overrideTab_checkEntry, undef, undef,
            9, 12, 9, 10,
            16, 16);
        $self->addLabel($grid,
            'Replacement tag - Standard, Xterm or RGB, or leave empty to use default colours',
            1, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, \&overrideTab_checkEntry2, undef, undef,
            9, 12, 10, 11,
            16, 16);
        # No text is an acceptable value, but the 'gtk-no' icon is currently visible. Force an
        #   update
        $self->setEntryIcon($entry2, TRUE);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'overrideHash',
            11,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($tag, $replace);

            $tag = $entry->get_text();
            $replace = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # To use the default colours, use an 'undef' value
                if ($replace eq '') {

                    $replace = undef;
                }

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('overrideHash', $tag, $replace);

                # Refresh the simple list and reset entry boxes
                $self->overrideTab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub overrideTab_refreshList {

        # Called by $self->overrideTab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

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

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

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('overrideHash');

        # Compile the simple list data
        foreach my $key (sort {lc($a) cmp lc($b)} (keys %ivHash)) {

            if (defined $ivHash{$key}) {
                push (@dataList, $key, $ivHash{$key});
            } else {
                push (@dataList, $key, '<use normal colour>');
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub overrideTab_checkEntry {

        # Callback from $self->overrideTab
        # Checks the contents of the Gtk3::Entry, returning FALSE or TRUE
        # Set up additional tabs for the notebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns FALSE if the contents are not acceptable, TRUE if they are acceptable

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

        # Local variables
        my $type;

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

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

        ($type) = $axmud::CLIENT->checkColourTags($text);
        if (! defined $type) {
            return FALSE;
        } else {
            return TRUE;
        }
    }

    sub overrideTab_checkEntry2 {

        # Callback from $self->overrideTab
        # Checks the contents of the Gtk3::Entry, returning FALSE or TRUE
        # Set up additional tabs for the notebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns FALSE if the contents are not acceptable, TRUE if they are acceptable

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

        # Local variables
        my $type;

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

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

        if ($text eq '') {

            return TRUE;

        } else {

            ($type) = $axmud::CLIENT->checkColourTags($text);
            if (! defined $type) {
                return FALSE;
            } else {
                return TRUE;
            }
        }
    }

    sub textTab {

        # Colour tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $count, $current,
            @list, @comboList,
            %descripHash,
        );

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

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

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Text settings');

        # Text settings
        $self->addLabel($grid, '<b>Text settings</b>',
            0, 12, 0, 1);

        # Font settings
        $self->addLabel($grid, '<i><u>Font settings</u></i>',
            1, 12, 1, 2);

        $self->addLabel($grid, 'Font',
            1, 2, 2, 3);

        my $entry = $self->addEntry($grid, undef, FALSE,
            2, 5, 2, 3);
        $entry->set_text($self->editObj->font . ' ' . $self->editObj->fontSize);

        my $button3 = $self->addButton($grid, 'Change', 'Change this font', undef,
            5, 6, 2, 3);
        $button3->signal_connect('clicked' => sub {

            my $font = $self->showFontSelectionDialogue(
                'Colour scheme \'' . $self->editObj->name . '\' font',
                $self->getEditHash_scalarIV('font') . ' ' . $self->getEditHash_scalarIV('fontSize'),
            );

            if (defined $font) {

                # $font is a string in the form 'Monospace 10'. Separate the font name from the
                #   size
                if ($font =~ m/(.*)\s(.\d)$/) {

                    $self->ivAdd('editHash', 'font', $1);
                    $self->ivAdd('editHash', 'fontSize', $2);

                    $entry->set_text($font);
                }
            }
        });

        my $entry2 = $self->addEntry($grid, undef, FALSE,
            6, 11, 2, 3);
        $entry2->set_text($axmud::CLIENT->constFont . ' ' . $axmud::CLIENT->constFontSize);

        my $button4 = $self->addButton($grid, 'Use default', 'Use the default font', undef,
            11, 12, 2, 3);
        $button4->signal_connect('clicked' => sub {

            $self->ivAdd('editHash', 'font', $axmud::CLIENT->constFont);
            $self->ivAdd('editHash', 'fontSize', $axmud::CLIENT->constFontSize);

            $entry->set_text($axmud::CLIENT->constFont . ' ' . $axmud::CLIENT->constFontSize);
        });

        # Word wrap settings
        $self->addLabel($grid, '<i><u>Word wrap settings</u></i>',
            1, 12, 3, 4);

        @list = (
            'no_wrap'           => 'Don\'t wrap text to fit the window',
            'wrap_char'         => 'Wrap text and split up words',
            'wrap_word'         => 'Wrap text and don\'t split up words',
            'wrap_word_char'    => 'Don\'t split up words unless it\'s necessary',
        );

        $count = -1;

        do {

            my ($mode, $descrip);

            $mode = shift @list;
            $descrip = shift @list;
            $count++;

            $descripHash{$descrip} = $mode;
            push (@comboList, $descrip);

            if ($mode eq $self->editObj->wrapMode) {

                $current = $count;
            }

        } until (! @list);

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            1, 12, 4, 5);
        if (defined $current) {

            $comboBox->set_active($current);
        }
        $comboBox->signal_connect('changed' => sub {

            my $descrip = $comboBox->get_active_text();

            $self->ivAdd('editHash', 'wrapMode', $descripHash{$descrip});
        });

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Component;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Component')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Component name', 'Component expected size', 'Component analyse mode'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->locatorOptionsTab();
        $self->otherOptionsTab();
        if ($self->editObj->type ne 'verb_special') {

            $self->patternsTagsTab();
        }

        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves the changes made to the component object. Check that any tags added are valid, and
        #   remove any that are invalid. (Dummy style tags are allowed, but not recommended.)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @ivList;

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

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

        # A list of the IVs which contain lists of colour/style tags
        @ivList = (
            'startTagList',
            'startNoTagList',
            'skipTagList',
            'stopBeforeTagList',
            'stopBeforeNoTagList',
            'stopAtTagList',
            'stopAtNoTagList',
        );

        if ($self->editHash) {

            foreach my $iv (@ivList) {

                my (
                    $listRef,
                    @firstList, @modList,
                );

                if ($self->ivExists('editHash', $iv)) {

                    $listRef = $self->ivShow('editHash', $iv);
                    # $listRef could conceivably contain multiple colour/style tags on the same
                    #   line. Compile a list, with each item being a distinct colour/style tag
                    foreach my $line (@$listRef) {

                        push (@firstList, split(m/\s+/, $line));
                    }

                    # Remove any invalid colour/style tags (dummy style tags are allowed, though not
                    #   recommended)
                    foreach my $tag (@firstList) {

                        my ($type) = $axmud::CLIENT->checkColourTags($tag);

                        if (
                            $axmud::CLIENT->ivExists('constStyleTagHash', $tag)
                            || $axmud::CLIENT->ivExists('constDummyTagHash', $tag)
                            || $type
                        ) {
                            push (@modList, $tag);
                        }
                    }

                    # Update the IV
                    $self->ivPoke('editHash', $iv, \@modList);
                }
            }

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -   The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $allTextMsg, $current, $matchFlag,
            @comboList,
        );

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

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

        # Tab setup (already created by the calling function)

        # Component name
        $self->addLabel($grid, '<b>Component name</b>',
            0, 6, 0, 1);
        $self->addEntry($grid, 'name', FALSE,
            1, 6, 1, 2, 16, 16);

        # Component type
        $self->addLabel($grid, '<b>Component type</b>',
            7, 13, 0, 1);
        $self->addEntry($grid, 'type', FALSE,
            8, 13, 1, 2, 16, 16);

        # Component expected size
        $self->addLabel($grid, '<b>Component expected size</b>',
            0, 13, 2, 3);

        $self->addLabel(
            $grid, 'Fixed size (range 0-256) (0 = use other properties to find the component)',
            1, 9, 3, 4);
        my $entry = $self->addEntryWithIcon($grid, 'size', 'int', 0, 256,
            9, 13, 3, 4);

        $self->addLabel($grid, 'Minimum size (range 0-256) (0 = this component is optional)',
            1, 9, 4, 5);
        my $entry2 = $self->addEntryWithIcon($grid, 'minSize', 'int', 0, 256,
            9, 13, 4, 5);

        $self->addLabel($grid, 'Maximum size (range 1-256)',
            1, 9, 5, 6);
        my $entry3 = $self->addEntryWithIcon($grid, 'maxSize', 'int', 1, 256,
            9, 13, 5, 6);

        if ($self->editObj->type eq 'mudlib_path') {

            # 'mudlib_path' component cannot be longer than 1 line; don't let user change the
            #   component size
            $entry->set_sensitive(FALSE);
            $entry2->set_sensitive(FALSE);
            $entry3->set_sensitive(FALSE);
        }

        # Component analyse mode
        $self->addLabel($grid, '<b>Component analyse mode</b>',
            0, 13, 6, 7);

        if ($self->editObj->type eq 'verb_special') {

            $self->addLabel(
                $grid,
                '<i>For this component, lines are only checked against special contents'
                . ' patterns stored in the world profile</i>',
                1, 13, 7, 8);

        } else {

            my ($group, $radioButton) = $self->addRadioButton(
                $grid, undef, '\'check_line\'', 'analyseMode',
                'check_line',   # IV set to this value when toggled
                TRUE,           # Sensitive widget
                1, 4, 8, 9);
            $self->addLabel(
                $grid,
                'Check each line, one at a time, against all patterns/tags <i>(recommended)</i>',
                4, 12, 8, 9);

            ($group, $radioButton) = $self->addRadioButton(
                $grid, $group, '\'check_pattern_tag\'', 'analyseMode', 'check_pattern_tag', TRUE,
                1, 4, 9, 10);
            $self->addLabel($grid, 'Check each pattern/tag, one at a time, against all lines',
                4, 12, 9, 10);

            $self->addLabel(
                $grid,
                '<i>NB Most patterns and tags are ignored if the component has a fixed size</i>',
                1, 13, 10, 11);
            $self->addLabel(
                $grid,
                '<i>NB The two sets of start patterns/tags are always checked together</i>',
                1, 13, 11, 12);

        }

        # Tab complete
        return 1;
    }

    sub locatorOptionsTab {

        # LocatorOptions tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $allTextMsg, $current, $matchFlag,
            @comboList,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Locator options',
            ['Locator task options', 'Locator task ignore options'],
        );

        # Locator task options
        $self->addLabel($grid, '<b>Locator task options</b>',
            0, 13, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Options which affect the way the Locator task interprets the text of a'
            . ' component</i>',
            1, 13, 1, 2);

        $self->addCheckButton(
            $grid,
            'Locator doesn\'t use normal/bold colour tags interchangeably',
            'boldSensitiveFlag',
            TRUE,
            1, 13, 2, 3);

        my $checkButton = $self->addCheckButton(
            $grid,
            'Locator only considers colours/styles that applied at the beginning of the line',
            'useInitialTagsFlag',
            TRUE,
            1, 13, 3, 4);

        my $checkButton2 = $self->addCheckButton(
            $grid,
            'Locator only considers colours/styles that were explicity specified on this line',
            'useExplicitTagsFlag',
            TRUE,
            1, 13, 4, 5);

        $self->addCheckButton(
            $grid,
            'Locator treats this component as if it were a single line',
            'combineLinesFlag',
            TRUE,
            1, 13, 5, 6);

        # Locator task ignore options
        $self->addLabel($grid, '<b>Locator task ignore options</b>',
            0, 13, 6, 7);
        $self->addLabel(
            $grid,
            '<i>Options which allow the Locator task to ignore some parts of a line (if you'
            . ' modify several options, only one is used)</i>',
            1, 13, 7, 8);

        $self->addLabel($grid, 'Locator ignores text that doesn\'t use this standard colour tag',
            1, 9, 8, 9);

        $allTextMsg = '(Use all colours)';
        @comboList = (
            $allTextMsg,
            $axmud::CLIENT->constColourTagList,
            $axmud::CLIENT->constBoldColourTagList,
        );

        foreach my $item ($axmud::CLIENT->constColourTagList) {

            push (@comboList, 'ul_' . $item);
        }

        foreach my $item ($axmud::CLIENT->constBoldColourTagList) {

            push (@comboList, 'UL_' . $item);
        }

        # (Don't use the generic combobox code so that the tags are always displayed in the same
        #   order)
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 13, 8, 9);

        $current = $self->getEditHash_scalarIV('useTextColour');
        if ($current) {

            # Display the current value of the IV in the combobox
            OUTER: for (my $count = 0; $count < scalar @comboList; $count++) {

                if ($comboList[$count] eq $current) {

                    $comboBox->set_active($count);
                    $matchFlag = TRUE;
                    last OUTER;
                }
            }

            if (! $matchFlag) {

                $comboBox->set_active(0);
            }
        }

       $comboBox->signal_connect('changed' => sub {

            my $text = $comboBox->get_active_text();

            # If the user has selected the title, ignore it
            if ($text eq $allTextMsg) {
                $self->ivAdd('editHash', 'useTextColour', undef);
            } else {
                $self->ivAdd('editHash', 'useTextColour', $text);
            }
        });

        $self->addLabel(
            $grid,
            'Locator ignores the first <i>n</i> characters (0 - use whole line)',
            1, 9, 9, 10);
        $self->addEntryWithIcon($grid, 'ignoreFirstChars', 'int', 0, undef,
            9, 13, 9, 10);
        $self->addLabel(
            $grid,
            'Locator uses only the first <i>n</i> characters (0 - use whole line)',
            1, 9, 10, 11);
        $self->addEntryWithIcon($grid, 'useFirstChars', 'int', 0, undef,
            9, 13, 10, 11);
        $self->addLabel(
            $grid,
            'Locator uses only the contents of any group substring(s), if this pattern matches',
            1, 9, 11, 12);
        $self->addEntryWithIconButton($grid, 'usePatternGroups', 'regex', 0, undef,
            1, 13, 12, 13);

        # Tab complete
        return 1;
    }

    sub otherOptionsTab {

        # OtherOptions tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $allTextMsg, $current, $matchFlag,
            @comboList,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Other options',
            ['Non-extractable components'],
        );

        # Non-extractable components
        $self->addLabel($grid, '<b>Non-extractable components</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of component types/names which should NOT be extracted, if this component can'
            . ' be extracted</i>',
            1, 12, 1, 2);

        $self->addTextView($grid, 'noExtractList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, TRUE, FALSE,    # Treat as list, remove empty lines, do remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub patternsTagsTab {

        # PatternsTags tab
        #
        # 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 . '->patternsTagsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Patterns / tags');

        # Add tabs to the inner notebook

        # Start patterns / tags (1/2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _1',
            'startPatternList', 'startTagList', 'startAllFlag', 'startTagMode',
            'Start patterns / tags (1/2)',
            'The start of the component DOES match one of these patterns',
            '...DOES contain one of these tags',
            'The start of the component DOES match all of these patterns',
            '...DOES contain all of these tags',
        );

        # Start patterns / tags (2/2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _2',
            'startNoPatternList', 'startNoTagList', 'startNoAllFlag', 'startNoTagMode',
            'Start patterns / tags (2/2)',
            'The start of the component does NOT match one of these patterns',
            '...does NOT contain one of these tags',
            'The start of the component does NOT match all of these patterns',
            '...does NOT contain all of these tags',
        );

        # Skip patterns / tags
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _3',
            'skipPatternList', 'skipTagList', 'skipAllFlag', 'skipTagMode',
            'Skip patterns / tags',
            'Patterns which mean the line should be ignored',
            'Tags found on an ignorable line',
            'Patterns which mean the line should be ignored',
            'Tags found on an ignorable line',
        );

        # Stop before patterns / tags (1/2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _4',
            'stopBeforePatternList', 'stopBeforeTagList', 'stopBeforeAllFlag',
                'stopBeforeTagMode',
            'Stop before patterns / tags (1/2)',
            'Stop before the first line which DOES match one of these patterns',
            '...DOES contain one of these tags',
            'Stop before the first line which DOES match all of these patterns',
            '...DOES contain all of these tags',
        );

        # Stop before patterns / tags (2/2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _5',
            'stopBeforeNoPatternList', 'stopBeforeNoTagList', 'stopBeforeNoAllFlag',
                'stopBeforeNoTagMode',
            'Stop before patterns / tags (2/2)',
            'Stop before the first line which does NOT match one of these patterns',
            '...does NOT contain one of these tags',
            'Stop before the first line which does NOT match all of these patterns',
            '...does NOT contain all of these tags',
        );

        # Stop at patterns / tags (1/2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _6',
            'stopAtPatternList', 'stopAtTagList', 'stopAtAllFlag', 'stopAtTagMode',
            'Stop at patterns / tags (1/2)',
            'Stop at the first line which DOES match one of these patterns',
            '...DOES contain one of these tags',
            'Stop at the first line which DOES match all of these patterns',
            '...DOES contain all of these tags',
        );

        # Stop at patterns / tags (2/2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _7',
            'stopAtNoPatternList', 'stopAtNoTagList', 'stopAtNoAllFlag', 'stopAtNoTagMode',
            'Stop at patterns / tags (2/2)',
            'Stop at the first line which does NOT match one of these patterns',
            '...does NOT contain one of these tags',
            'Stop at the first line which does NOT match all of these patterns',
            '...does NOT contain all of these tags',
        );

        # Stop at capitalised lines
        $self->patternsTags8Tab($innerNotebook);
        # Stop before mode
        $self->patternsTags9Tab($innerNotebook);
        # Stop at mode
        $self->patternsTags10Tab($innerNotebook);

        return 1;
    }

    sub patternsTags1Tab {

        # PatternsTags1 tab
        #
        # Expected arguments
        #   $innerNotebook
        #       - The Gtk3::Notebook object inside $self->notebook
        #   $tabName
        #       - e.g. 'Page _1'
        #   $patternListIV, $tagListIV, $flagIV, $modeIV
        #       - The names of the IVs being set (e.g. 'startPatternList', 'startTagList',
        #               'startAllFlag', 'startTagMode'
        #   @stringList
        #       - List containing five strings. The first is used for the main Gtk3::Label at the
        #           top of the tab; two out of the remaining four are used in the other Gtk3::Labels
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my (
            $self, $innerNotebook, $tabName, $patternListIV, $tagListIV, $flagIV, $modeIV,
            @stringList,
        ) = @_;

        # Local variables
        my (@comboList, @comboList2, @comboList3);

        # Check for improper arguments
        if (
            ! defined $innerNotebook || ! defined $tabName || ! defined $patternListIV
            || ! defined $tagListIV || ! defined $flagIV || ! defined $modeIV
            || scalar @stringList != 5
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->patternsTags1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            $tabName,
            [$stringList[0]],
        );

        # Title label, e.g. Start patterns / tags
        $self->addLabel($grid, '<b>' . $stringList[0] . '</b>',
            0, 4, 0, 1);

        # First group of radio buttons (at top; ->signal_connects appear below)
        my ($group, $radioButton, $radioButton2);
        ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, 'Line contains at least one pattern / tag', undef, undef, TRUE,
            4, 9, 0, 1);
        ($group, $radioButton2) = $self->addRadioButton(
            $grid, $group, 'All patterns / tags', undef, undef, TRUE,
            9, 12, 0, 1);
        if ($self->editObj->$flagIV) {

            $radioButton2->set_active(TRUE);
        }

        # Second group of radio buttons (in middle; ->signal_connects appear below)
        my ($group2, $radioButton11, $radioButton12, $radioButton13, $radioButton14);
        ($group2, $radioButton11) = $self->addRadioButton(
            $grid, undef, 'Any tags', undef, undef, TRUE,
            4, 6, 6, 7);
        ($group2, $radioButton12) = $self->addRadioButton(
            $grid, $group2, 'No colours', undef, undef, TRUE,
            6, 8, 6, 7);
        if ($self->editObj->$modeIV eq 'no_colour') {

            $radioButton12->set_active(TRUE);
        }
        ($group2, $radioButton13) = $self->addRadioButton(
            $grid, $group2, 'No styles', undef, undef, TRUE,
            8, 10, 6, 7);
        if ($self->editObj->$modeIV eq 'no_style') {

            $radioButton13->set_active(TRUE);
        }
        ($group2, $radioButton14) = $self->addRadioButton(
            $grid, $group2, 'No colours/styles', undef, undef, TRUE,
            10, 12, 6, 7);
        if ($self->editObj->$modeIV eq 'no_colour_style') {

            $radioButton14->set_active(TRUE);
        }

        # First label and textview, e.g. 'Patterns which mark the start of the component'
        my $label = $self->addLabel($grid, '',
            1, 12, 1, 2);
        my $textView = $self->addTextView($grid, $patternListIV, TRUE,
            1, 10, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addRegexButton($grid,
            [
                'list', $patternListIV,
            ],
            10, 12, 2, 3);
        $self->addLabel($grid, '',    # Empty labels for spacing
            10, 12, 3, 4);
        $self->addLabel($grid, '',    # Empty labels for spacing
            10, 12, 4, 5);
        $self->addLabel($grid, '',    # Empty labels for spacing
            10, 12, 5, 6);

        # Second label and textview, e.g. 'Tags found at the start of the component'
        my $label2 = $self->addLabel($grid, '',
            1, 4, 6, 7);
        my $textView2 = $self->addTextView($grid, $tagListIV, TRUE,
            1, 4, 7, 12,
            TRUE, TRUE, TRUE, FALSE);  # Treat as list, remove empty lines, do remove whitespace
        my $buffer = $textView2->get_buffer();

        # (Set text for these two labels)
        if (! $self->getEditHash_scalarIV($flagIV)) {

            $label->set_markup('<i>' . $stringList[1] . '</i>');
            $label2->set_markup('<i>' . $stringList[2] . '</i>');

        } else {

            $label->set_markup('<i>' . $stringList[3] . '</i>');
            $label2->set_markup('<i>' . $stringList[4] . '</i>');
        }

        # ->signal_connects for first group of radiobuttons
        $radioButton->signal_connect('toggled' => sub {

            # Set the IV only if this radiobutton has been selected
            if ($radioButton->get_active()) {

                $self->ivAdd('editHash', $flagIV, FALSE);

                $label->set_markup('<i>' . $stringList[1] . '</i>');
                $label2->set_markup('<i>' . $stringList[2] . '</i>');
            }
        });

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

            # Set the IV only if this radiobutton has been selected
            if ($radioButton2->get_active()) {

                $self->ivAdd('editHash', $flagIV, TRUE);

                $label->set_markup('<i>' . $stringList[3] . '</i>');
                $label2->set_markup('<i>' . $stringList[4] . '</i>');
            }
        });

        # Prepare lists for insertion into comboboxes
        @comboList = $axmud::CLIENT->constColourTagList;
        foreach my $item (@comboList) {

            push (@comboList2, 'ul_' . $item);
        }
        @comboList3 = $axmud::CLIENT->constStyleTagList;

        # Add editing combos / buttons in bottom-right corner
        # First set - standard text colour tags
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, 'Standard text colour tags:',
            TRUE,               # No 'undef' value used
            4, 10, 7, 8);
        my $button = $self->patternsTags1Tab_addButton(
            $grid,
            7,
            $tagListIV,
            $buffer,
            $comboBox,
            'Standard text colour tags:',
        );
        my $button2 = $self->patternsTags1Tab_addBoldButton(
            $grid,
            7,
            $tagListIV,
            $buffer,
            $comboBox,
            'Standard text colour tags:',
        );

        # Second set - standard underlay colour tags
        my $comboBox2 = $self->addComboBox(
            $grid,
            undef,
            \@comboList2,
            'Standard underlay colour tags:',
            TRUE,               # No 'undef' value used
            4, 10, 8, 9);
        my $button3 = $self->patternsTags1Tab_addButton(
            $grid,
            8,
            $tagListIV,
            $buffer,
            $comboBox2,
            'Standard underlay colour tags:',
        );
        my $button4 = $self->patternsTags1Tab_addBoldButton(
            $grid,
            8,
            $tagListIV,
            $buffer,
            $comboBox2,
            'Standard underlay colour tags:',
        );

        # Third set - xterm tags
        $self->addLabel($grid, 'xterm tags:',
            4, 6, 9, 10);
        my $entry = $self->addEntryWithIcon(
            $grid,
            undef,
            \&patternsTags1Tab_checkEntry,
            undef,
            undef,
            6, 10, 9, 10,
            8, 8);

        # (Only one button of this type, for this tab, so it doesn't have its own function)
        my $button5 = $self->addButton(
            $grid,
            'Add',
            'Add the selected xterm tag to the list',
            undef,
            10, 11, 9, 10,
        );
        $button5->signal_connect('clicked' => sub {

            my (
                $tag, $first, $second, $num, $modTag,
                @ivList,
            );

            if ($self->checkEntryIcon($entry)) {

                $tag = $entry->get_text();

                # Convert $tag to use standard capitalisation: xterm colour tags in the range
                #   'x0' to 'x255' or 'ux0' to 'ux255'
                $first = substr($tag, 0, 1);
                $second = substr($tag, 0, 2);
                if ($first eq 'x' || $first eq 'X') {

                    # (Make sure the nnn part is an integer without leading zeros, e.g. convert
                    #   001 to 1)
                    $num = substr($tag, 1);
                    $num += 0;
                    $modTag = 'x' . $num;

                } elsif ($second eq 'ux' || $second eq 'UX') {

                    $num = substr($tag, 2);
                    $num += 0;
                    $modTag = 'ux' . $num;
                }

                # Update the IV
                @ivList = $self->getEditHash_listIV($tagListIV);

                push (@ivList, $modTag);
                $self->ivAdd('editHash', $tagListIV, \@ivList);

                # Update the textview
                $buffer->set_text(join("\n", @ivList));
            }
        });

        # Fourth set - rgb tags
        $self->addLabel($grid, 'RGB tags:',
            4, 6, 10, 11);
        my $entry2 = $self->addEntryWithIcon(
            $grid,
            undef,
            \&patternsTags1Tab_checkEntry2,
            undef,
            undef,
            6, 10, 10, 11,
            8, 8);

        # (Only one button of this type, for this tab, so it doesn't have its own function)
        my $button6 = $self->addButton(
            $grid,
            'Add',
            'Add the selected xterm tag to the list',
            undef,
            10, 11, 10, 11,
        );
        $button6->signal_connect('clicked' => sub {

            my (
                $tag, $first, $second, $num, $modTag,
                @ivList,
            );

            if ($self->checkEntryIcon($entry2)) {

                $tag = $entry2->get_text();

                # Convert $tag to use standard capitalisation: RGB colour tags in the range
                #   '#000000' to '#FFFFFF' or 'u#000000' to 'u#FFFFFF'
                $first = substr($tag, 0, 1);
                $second = substr($tag, 0, 2);
                if ($first eq '#') {

                    $modTag = uc($tag);

                } elsif ($second eq 'u#' || $second eq 'U#') {

                    $modTag = 'u#' . uc(substr($tag, 2));
                }

                # Update the IV
                @ivList = $self->getEditHash_listIV($tagListIV);

                push (@ivList, $modTag);
                $self->ivAdd('editHash', $tagListIV, \@ivList);

                # Update the textview
                $buffer->set_text(join("\n", @ivList));
            }
        });

        # Fifth set - style tags
        # NB 'Dummy' style tags can be added to the GA::Obj::Component object, although this is not
        #   recommended, so they are not included in the combobox
        my $comboBox3 = $self->addComboBox($grid, undef, \@comboList3, 'Style tags:',
            TRUE,               # No 'undef' value used
            4, 10, 11, 12);
        my $button7 = $self->patternsTags1Tab_addButton(
            $grid,
            11,
            $tagListIV,
            $buffer,
            $comboBox3,
            'Style tags:',
        );

        # Set initial sensitivity for these widgets
        $self->patternsTags1Tab_setSensitive(
            $self->getEditHash_scalarIV($modeIV),
            $textView2,
            [$comboBox, $button, $button2],
            [$comboBox2, $button3, $button4],
            [$entry, $button5],
            [$entry2, $button6],
            [$comboBox3, $button7],
        );

        # ->signal_connects for second group of radiobuttons
        $radioButton11->signal_connect('toggled' => sub {

            # Set the IV only if this radiobutton has been selected
            if ($radioButton11->get_active()) {

                $self->ivAdd('editHash', $modeIV, 'default');   # Any tags

                $self->patternsTags1Tab_setSensitive(
                    'default',
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$entry2, $button6],
                    [$comboBox3, $button6],
                );
            }
        });

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

            # Set the IV only if this radiobutton has been selected
            if ($radioButton12->get_active()) {

                $self->ivAdd('editHash', $modeIV, 'no_colour');

                $self->patternsTags1Tab_setSensitive(
                    'no_colour',
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$entry2, $button6],
                    [$comboBox3, $button6],
                );
            }
        });

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

            # Set the IV only if this radiobutton has been selected
            if ($radioButton13->get_active()) {

                $self->ivAdd('editHash', $modeIV, 'no_style');

                $self->patternsTags1Tab_setSensitive(
                    'no_style',
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$entry2, $button6],
                    [$comboBox3, $button6],
                );
            }
        });

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

            # Set the IV only if this radiobutton has been selected
            if ($radioButton14->get_active()) {

                $self->ivAdd('editHash', $modeIV, 'no_colour_style');

                $self->patternsTags1Tab_setSensitive(
                    'no_colour_style',
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$entry2, $button6],
                    [$comboBox3, $button6],
                );
            }
        });

        # Tab complete
        return 1;
    }

    sub patternsTags1Tab_checkEntry {

        # Called by $self->patternsTags1Tab to check the text in the first Gtk3::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk3::Entry
        #
        # Return values
        #   'undef' on improper arguments or if $text is invalid
        #   1 if $text is valid

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

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

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

        # $text can be an xterm colour tag (in the range 'x0' to 'x255', or 'ux0' to 'ux255'; xterm
        #   tags are case-insensitive
        if ($text =~ m/^u?x([0-9]+)$/i) {

            # (Don't permit 'x000005', but do permit 'x005' or 'x5')
            if ($1 >= 0 && $1 <= 255 && length ($1) <= 3) {
                return 1;
            } else {
                return undef;
            }

        } else {

            return undef;
        }
    }

    sub patternsTags1Tab_checkEntry2 {

        # Called by $self->patternsTags1Tab to check the text in the second Gtk3::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk3::Entry
        #
        # Return values
        #   'undef' on improper arguments or if $text is invalid
        #   1 if $text is valid

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

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

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

        # $text can be an RGB colour tag (in the range '#000000' to '#FFFFFF', or 'u#000000' to
        #   'u#FFFFFF'; RGB tags are case-insensitive
        if ($text =~ m/^u?\#[0-9A-F]{6}$/i) {
            return 1;
        } else {
            return undef;
        }
    }

    sub patternsTags1Tab_addButton {

        # Called by $self->patternsTags1Tab to add a Gtk3::Button next to a combobox
        #
        # Expected arguments
        #   $grid           - The Gtk3::Grid for this tab
        #   $row            - The position in the grid of the buttons
        #   $iv             - The IV being edited (e.g. 'startTagList')
        #   $buffer         - The buffer of the Gtk3::TextView used to display the IV
        #   $comboBox       - The Gtk3::ComboBox used alongside the Gtk3::Button
        #   $title          - The title used at the top of the combobox
        #
        # Return values
        #   'undef' on improper arguments
        #   The Gtk3::Button created, otherwise

        my ($self, $grid, $row, $iv, $buffer, $comboBox, $title, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $grid || ! defined $row || ! defined $iv || ! defined $buffer
            || ! defined $comboBox || ! defined $title || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->patternsTags1Tab_addButton',
                @_,
            );
        }

        my $button = $self->addButton(
            $grid,
            'Add',
            'Add the selected colour tag to the list',
            undef,
            10, 11, $row, ($row + 1),
        );
        $button->signal_connect('clicked' => sub {

            my (
                $tag,
                @ivList,
            );

            $tag = $comboBox->get_active_text();

            if ($tag && $tag ne $title) {

                # Update the IV
                @ivList = $self->getEditHash_listIV($iv);

                push (@ivList, $tag);
                $self->ivAdd('editHash', $iv, \@ivList);

                # Update the textview
                $buffer->set_text(join("\n", @ivList));
            }
        });

        return $button;
    }

    sub patternsTags1Tab_addBoldButton {

        # Called by $self->patternsTags1Tab to add another Gtk3::Button next to a combobox
        #
        # Expected arguments
        #   $grid           - The Gtk3::Grid for this tab
        #   $row            - The position in the grid of the buttons
        #   $iv             - The IV being edited (e.g. 'startTagList')
        #   $buffer         - The buffer of the Gtk3::TextView used to display the IV
        #   $comboBox       - The Gtk3::ComboBox used alongside the Gtk3::Button
        #   $title          - The title used at the top of the combobox
        #
        # Return values
        #   'undef' on improper arguments
        #   The Gtk3::Button created, otherwise

        my ($self, $grid, $row, $iv, $buffer, $comboBox, $title, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $grid || ! defined $row || ! defined $iv || ! defined $buffer
            || ! defined $comboBox || ! defined $title || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->patternsTags1Tab_addBoldButton',
                @_,
            );
        }

        my $button = $self->addButton(
            $grid,
            'Add bold',
            'Add the selected bold colour tag to the list',
            undef,
            11, 12, $row, ($row + 1),
        );
        $button->signal_connect('clicked' => sub {

            my (
                $tag,
                @ivList,
            );

            $tag = $comboBox->get_active_text();

            if ($tag && $tag ne $title) {

                # Update the IV
                @ivList = $self->getEditHash_listIV($iv);

                push (@ivList, uc($tag));
                $self->ivAdd('editHash', $iv, \@ivList);

                # Update the textview
                $buffer->set_text(join("\n", @ivList));
            }
        });

        return $button;
    }

    sub patternsTags1Tab_setSensitive {

        # Called by $self->patternsTags1Tab to check the text in the Gtk3::Entry
        #
        # Expected arguments
        #   $mode       - Matches a setting for $self->editObj->startTagMode, ->stopAtTagMode, etc
        #                   (set to one of 'default', 'no_colour', 'no_style', 'no_colour_style')
        #   $textView   - (Only) textview to be sensitised/desensitised
        #   $listRef, $listRef2, $listRef3, $listRef4, $listRef5
        #               - References to lists of widgets to be sensitised/desensitised
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my (
            $self, $mode, $textView, $listRef, $listRef2, $listRef3, $listRef4, $listRef5,
            $check,
        ) = @_;

        # Check for improper arguments
        if (
            ! defined $mode || ! defined $textView || ! defined $listRef || ! defined $listRef2
            || ! defined $listRef3 || ! defined $listRef4 || ! defined $listRef5 || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->patternsTags1Tab_setSensitive',
                @_,
            );
        }

        # Textview
        if ($mode ne 'no_colour_style') {
            $textView->set_sensitive(TRUE);
        } else {
            $textView->set_sensitive(FALSE);
        }

        # First-fourth row
        if ($mode eq 'default' || $mode eq 'no_style') {

            foreach my $widget (@$listRef, @$listRef2, @$listRef3, @$listRef4) {

                $widget->set_sensitive(TRUE);
            }

        } else {

            foreach my $widget (@$listRef, @$listRef2, @$listRef3, @$listRef4) {

                $widget->set_sensitive(FALSE);
            }
        }

        # Fifth row
        if ($mode eq 'default' || $mode eq 'no_colour') {

            foreach my $widget (@$listRef5) {

                $widget->set_sensitive(TRUE);
            }

        } else {

            foreach my $widget (@$listRef5) {

                $widget->set_sensitive(FALSE);
            }
        }

        return 1;
    }

    sub patternsTags7Tab {

        # PatternsTags7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@comboList, @comboList2, @comboList3);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Stop at patterns (2/2)'],
        );

        # Stop at patterns (2/2)
        $self->addLabel($grid, '<b>Stop at patterns (2/2)</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Stop at the first line which DOES match one of these patterns</i>',
            1, 12, 1, 2);

        $self->addTextView($grid, 'stopAtPatternList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        # Stop at tags (2/2)
        $self->addLabel($grid, '<b>Stop at tags (2/2)</b>',
            0, 12, 6, 7);
        $self->addLabel($grid,
            '<i>Stop at the first line which DOES contain one of these ' . $axmud::SCRIPT
            . ' colour/style tags</i>',
            1, 12, 7, 8);

        my $textView = $self->addTextView($grid, 'stopAtTagList', TRUE,
            1, 6, 8, 12);
        my $buffer = $textView->get_buffer();

        @comboList = $axmud::CLIENT->constColourTagList;
        @comboList3 = $axmud::CLIENT->constStyleTagList;
        foreach my $item (@comboList) {

            push (@comboList2, 'ul_' . $item);
        }

        # Add editing combos / buttons
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, 'Colour tags',
            TRUE,               # No 'undef' value used
            7, 11, 8, 9);
        my $button = $self->patternsTags1Tab_addButton(
            $grid,
            11, 8,
            'stopAtTagList',
            $buffer,
            $comboBox,
            'Colour tags:',
        );

        my $comboBox2 = $self->addComboBox($grid, undef, \@comboList2, 'Underlay tags',
            TRUE,               # No 'undef' value used
            7, 11, 9, 10);
        my $button2 = $self->patternsTags1Tab_addButton(
            $grid,
            11, 9,
            'stopAtTagList',
            $buffer,
            $comboBox2,
            'Underlay tags:',
        );

        my $comboBox3 = $self->addComboBox($grid, undef, \@comboList3, 'Style tags',
            TRUE,               # No 'undef' value used
            7, 11, 10, 11);
        my $button3 = $self->patternsTags1Tab_addButton(
            $grid,
            11, 10,
            'stopAtTagList',
            $buffer,
            $comboBox3,
            'Style tags:',
        );

        # Tab complete
        return 1;
    }

    sub patternsTags8Tab {

        # PatternsTags8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Stop at capitalised lines'],
        );

        # Stop at capitalised lines
        $self->addLabel($grid, '<b>Stop at capitalised lines</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            'Stop at the n-th line which starts with a capital letter (0 - ignore)',
            1, 8, 1, 2);
        $self->addEntryWithIcon($grid, 'upperCount', 'int', 0, 16,
            8, 12, 1, 2);

        $self->addLabel($grid,
            'Stop at the n-th line which starts with a lower-case letter or number (0 - ignore)',
            1, 8, 2, 3);
        $self->addEntryWithIcon($grid, 'otherCount', 'int', 0, 16,
            8, 12, 2, 3);

        # Tab complete
        return 1;
    }

    sub patternsTags9Tab {

        # PatternsTags9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _9',
            ['Stop before mode'],
        );

        # Stop before mode
        $self->addLabel($grid, '<b>Stop before mode</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'default\'', 'stopBeforeMode',
            'default',      # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel($grid, 'Don\'t use \'Stop before mode\'',
            4, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'no_char\'', 'stopBeforeMode', 'no_char', TRUE,
            1, 4, 2, 3);
        $self->addLabel(
            $grid, 'Stop 1 line before 1st line containing no characters at all',
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'no_letter_num\'', 'stopBeforeMode', 'no_letter_num', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $grid, 'Stop 1 line before 1st line containing no alphanumeric characters',
            4, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid,
            $group,
            '\'no_start_letter_num\'',
            'stopBeforeMode',
            'no_start_letter_num',
            TRUE,
            1, 4, 4, 5);
        $self->addLabel(
            $grid,
            'Stop 1 line before 1st line which doesn\'t start with an alphanumeric'
            . ' character',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'no_tag\'', 'stopBeforeMode', 'no_tag', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $grid,
            'Stop 1 line before 1st line containing no colour/style tags at all',
            4, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'has_letter_num\'', 'stopBeforeMode', 'has_letter_num', TRUE,
            1, 4, 6, 7);
        $self->addLabel(
            $grid,
            'Stop 1 line before 1st line which DOES contain alphanumeric characters',
            4, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $grid,
            $group,
            '\'has_start_letter_num\'',
            'stopBeforeMode',
            'has_start_letter_num',
            TRUE,
            1, 4, 7, 8);
        $self->addLabel(
            $grid,
            'Stop 1 line before 1st line which DOES start with an alphanumeric character',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'has_tag\'', 'stopBeforeMode', 'has_tag', TRUE,
            1, 4, 8, 9);
        $self->addLabel(
            $grid,
            'Stop 1 line before 1st line which DOES contain colour/style tags',
            4, 12, 8, 9);

        # Tab complete
        return 1;
    }

    sub patternsTags10Tab {

        # PatternsTags10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 1_0',
            ['Stop at mode'],
        );

        # Stop at mode
        $self->addLabel($grid, '<b>Stop at mode</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'default\'', 'stopAtMode',
            'default',      # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel($grid, 'Don\'t use \'Stop at mode\'',
            4, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'no_char\'', 'stopAtMode', 'no_char', TRUE,
            1, 4, 2, 3);
        $self->addLabel(
            $grid, 'Stop at the 1st line containing no characters at all',
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'no_letter_num\'', 'stopAtMode', 'no_letter_num', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $grid, 'Stop at the 1st line containing no alphanumeric characters',
            4, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'no_start_letter_num\'', 'stopAtMode', 'no_start_letter_num', TRUE,
            1, 4, 4, 5);
        $self->addLabel(
            $grid, 'Stop at the 1st line which doesn\'t start with an alphanumeric character',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'no_tag\'', 'stopAtMode', 'no_tag', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $grid,
            'Stop at the 1st line containing no colour/style tags at all',
            4, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'has_letter_num\'', 'stopAtMode', 'has_letter_num', TRUE,
            1, 4, 6, 7);
        $self->addLabel(
            $grid, 'Stop at the 1st line which DOES contain alphanumeric characters',
            4, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'has_start_letter_num\'', 'stopAtMode', 'has_start_letter_num', TRUE,
            1, 4, 7, 8);
        $self->addLabel(
            $grid, 'Stop at the 1st line which DOES start with an alphanumeric character',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'has_tag\'', 'stopAtMode', 'has_tag', TRUE,
            1, 4, 8, 9);
        $self->addLabel(
            $grid,
            'Stop at the 1st line which DOES contain colour/style tags',
            4, 12, 8, 9);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Dict;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Dict')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook, once the user has specified the dictionary's name
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Dictionary name', 'Language', 'Word order'],
        );

        # Dictionary name
        $self->addLabel($grid, '<b>Dictionary name</b>',
            0, 6, 0, 1);

        # Explanatory label - contents to be set to 'Choose a name and press enter', in a moment
        my $label = $self->addLabel($grid, '',
            1, 6, 2, 3);

        # Entry box for the dictionary name
        my $entry;

        if ($self->tempFlag) {

            # $self->editObj is a temporary dictionary, so the entry box must be sensitive
            $entry = $self->addEntry($grid, undef, TRUE,
                1, 6, 1, 2, 16, 16);

            $entry->signal_connect (activate => sub {

                my $text = $entry->get_text();

                # Check that the name is acceptable
                if ($axmud::CLIENT->ivExists('dictHash', $text)) {

                    $label->set_markup('<i>Dictionary named \'' . $text . '\' already exists</i>');
                    $self->winShowAll($self->_objClass . '->setupNotebook');

                } elsif (! $axmud::CLIENT->nameCheck($text, 16)) {

                    $label->set_markup('<i>Max 16 chars: A-Z a-z _ 0-9</i>');
                    $self->winShowAll($self->_objClass . '->setupNotebook');

                } else {

                    # Replace the explanatory label
                    $label->set_markup('<i>You may now edit the dictionary</i>');
                    # Desensitise the entry box - once the dictionary name is entered, it's final
                    $entry->set_sensitive(FALSE);
                    # Make sure the OK/reset/save buttons are now sensitised
                    $self->okButton->set_sensitive(TRUE);
                    $self->resetButton->set_sensitive(TRUE);
                    $self->saveButton->set_sensitive(TRUE);

                    # Store the name until all IVs are copied to the profile
                    $self->ivAdd('editHash', 'name', $text);

                    # Set up the remaining tabs
                    $self->expandNotebook();

                    # Render the changes
                    $self->winShowAll($self->_objClass . '->setupNotebook');
                }
            });

            $entry->signal_connect('changed' => sub {

                my ($value, $result);

                $value = $entry->get_text();
                # Check whether $text is a valid value, or not
                if ($axmud::CLIENT->nameCheck($value, 16)) {
                    $entry->set_icon_from_stock('secondary', 'gtk-yes');
                } else {
                    $entry->set_icon_from_stock('secondary', 'gtk-no');
                }
            });

            $label->set_markup('<i>Choose a name and press \'enter\'</i>');
            # (Entry is initially empty)
            $entry->set_icon_from_stock('secondary', 'gtk-no');

        } else {

            # $self->editObj is an existing dictionary, so the entry box must be insensitive
            $entry = $self->addEntry($grid, 'name', FALSE,
                1, 6, 1, 2, 16, 16);

            $label->set_markup('<i>(The dictionary name can\'t be changed)</i>');

            # Set up the remaining tabs
            $self->expandNotebook();
        }

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        # If this is a current dictionary, set a flag
        if ($self->session->currentDict eq $self->editObj) {

            $self->ivPoke('currentFlag', TRUE);
        }

        # Expand the notebook
        $self->articlesTab();
        $self->conjunctionsTab();
        $self->numbersTab();
        $self->timeTab();
        $self->nounsTab();
        $self->pluralsTab();
        $self->adjectivesTab();
        $self->pseudoTab();
        $self->contentsTab();
        $self->directionsTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves the changes made to the dictionary. If it was a temporary dictionary, create a real
        #   dictionary, and save the changes to the latter
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # For a temporary dictionary...
            if ($self->tempFlag) {

                my ($dictName, $result, $dictObj, $number);

                $dictName = $self->ivShow('editHash', 'name');

                # The user made changes, so we can now create a permanent dictionary and give it IVs
                #   specified in $self->editHash
                $result = $self->session->pseudoCmd(
                    'adddictionary ' . $dictName,
                    $self->pseudoCmdMode,
                );

                if ($result) {

                    # Get the blessed reference of the new dictionary
                    $dictObj = $axmud::CLIENT->ivShow('dictHash', $dictName);

                    # Store the changes the user has made (ignore the 'name' IV, which has aleady
                    #   been set)
                    $self->ivDelete('editHash', 'name');

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

                        $dictObj->{$key} = $self->ivShow('editHash', $key);
                    }

                    # If the 'save' button was clicked, the window won't be closed. Before
                    #   re-drawing all those tabs, set the object being edited to the one we just
                    #   created
                    $self->ivPoke('editObj', $dictObj);
                    $self->ivPoke('tempFlag', FALSE);
                }

            # For an existing dictionary...
            } else {

                # Store the changes the user has made
                foreach my $key ($self->ivKeys('editHash')) {

                    $self->editObj->{$key} = $self->ivShow('editHash', $key);
                }

                # Update the dictionary's combined hashes. The FALSE argument means that the
                #   dictionary's parent file object doesn't need to have its ->modifyFlag set
                #   (because we're about to do it anyway)
                $self->editObj->createCombHashes($self->session, FALSE);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @pbList, @comboList,
            %comboHash,
        );

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

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

        # Tab setup (already created by the calling function)

        # Current dictionary
        $self->addLabel($grid, '<b>Current dictionary</b>',
            7, 12, 0, 1);

        my $checkButton = Gtk3::CheckButton->new();
        if ($self->session->currentDict eq $self->editObj) {

            $checkButton->set_active(TRUE);
        }
        $checkButton->set_sensitive(FALSE);
        $grid->attach($checkButton, 8, 1, 1, 1);

        # Language
        $self->addLabel($grid, '<b>Language</b>',
            0, 6, 3, 4);
        $self->addEntryWithIcon($grid, 'language', 'string', 1, undef,
            1, 6, 4, 5, 16, 16);

        @pbList = sort {lc($a->targetName) cmp lc($b->targetName)}
            ($axmud::CLIENT->ivValues('constPhrasebookHash'));

        foreach my $pbObj (@pbList) {

            push (@comboList, $pbObj->targetName);
            $comboHash{$pbObj->targetName} = $pbObj;
        }

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            1, 4, 5, 6);
        my $button = $self->addButton(
            $grid,
            'Switch language',
            'Switch language and update some dictionary data',
            undef,
            4, 6, 5, 6,
            TRUE);          # Irreversible
        $button->signal_connect('clicked' => sub {

            my ($targetName, $pbObj, $choice);

            $targetName = $combo->get_active_text();
            $pbObj = $comboHash{$targetName};

            # Prompt user
            $choice = $self->showMsgDialogue(
                'Switch language',
                'question',
                'Are you sure you want to switch to ' . ucfirst($pbObj->name) . '?\ (This action'
                . ' will save any changes you\'ve already made, and then replace the dictionary\'s'
                . ' primary directions, articles, conjunctions and some number words)',
                'yes-no',
            );

            if (defined $choice && $choice eq 'yes') {

                # Update the dictionary saving any changes made in this 'edit' window
                $self->saveChanges();

                # Perform the operation
                $self->editObj->uploadPhrasebook($pbObj);

                # Redraw the edit window
                $self->buttonReset();
            }
        });

        # Word order
        $self->addLabel($grid, '<b>Word order</b>',
            7, 12, 3, 4);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'noun_adj\'', 'nounPosn',
            'noun_adj',     # IV set to this value when toggled
            TRUE,           # Sensitive widget
            8, 10, 4, 5);
        $self->addLabel($grid, 'Noun-adjective (e.g. French, Spanish)',
            10, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'adj_noun\'', 'nounPosn', 'adj_noun', TRUE,
            8, 10, 5, 6);
        $self->addLabel($grid, 'Adjective-noun (e.g. English, German)',
            10, 12, 5, 6);

        # Tab complete
        return 1;
    }

    sub articlesTab {

        # Articles tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @list;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Articles',
            ['Definite articles', 'Indefinite articles'],
        );

        # Definite articles
        $self->addLabel($grid, '<b>Definite articles</b>',
            0, 12, 0, 2);
        $self->addLabel($grid, '<i>Words for \'the\' in the dictionary\'s language</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'definiteList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Indefinite articles
        $self->addLabel($grid, '<b>Indefinite articles</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Words for \'a\' / \'an\' in the dictionary\'s language</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'indefiniteList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub conjunctionsTab {

        # Conjunctions tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @list;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'Con_junctions',
            ['Conjunctions'],
        );

        # Conjunctions
        $self->addLabel($grid, '<b>Conjunctions</b>',
            0, 12, 0, 2);
        $self->addLabel($grid, '<i>Words for \'and\' in the dictionary\'s language</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'andList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, '<i>Words for \'or\' in the dictionary\'s language</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'orList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub numbersTab {

        # Numbers tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'N_umbers',
            ['Number terms'],
        );

        # Number terms
        $self->addLabel($grid, '<b>Number terms</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of terms for ordinal numbers, fractions and indeterminate numbers</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Term', 'text',
            'Number', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'numberHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Term',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Number (indeterminate = -1):',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add buttons that resemble those from GA::Generic::EditWin->addSimpleListButtons_hashIV
        my $button = $self->addButton($grid, 'Add term', 'Add a new number term', undef,
            1, 3, 9, 10);
        $button->signal_connect('clicked' => sub {

            my ($term, $value);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $term = $entry->get_text();
                $value = $entry2->get_text();

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('numberHash', $term, $value);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'numberHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        my $button2 = $self->addButton($grid, 'Delete term', 'Delete the selected term', undef,
            6, 8, 9, 10);
        $button2->signal_connect('clicked' => sub {

            my ($term) = $self->getSimpleListData($slWidget, 0);
            if (defined $term) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('numberHash', $term, undef, TRUE);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'numberHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        my $button3 = $self->addButton($grid,
            'Reset', 'Reset the hash which stores the terms', undef,
            8, 10, 9, 10);
        $button3->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'numberHash');

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'numberHash');
            $self->resetEntryBoxes($entry, $entry2);
        });

        my $button4 = $self->addButton($grid,
            'Clear', 'Clear the hash which stores the terms', undef,
            10, 12, 9, 10);
        $button4->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'numberHash', {});

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'numberHash');
            $self->resetEntryBoxes($entry, $entry2);
        });

        # Tab complete
        return 1;
    }

    sub numbersTab_refreshList {

        # Called by $self->numbersTab to refresh the GA::Obj::SimpleList, ordering the data by
        #   each key-value pair's value (instead of ordering it by key, as usual)
        # Also called by $self->clock1Tab, ->clock2Tab and ->clock3Tab to do the same job
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $iv         - The IV being edited
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $iv, $check) = @_;

        # Local variables
        my (
            @sortedList, @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $iv || defined $check) {

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

        # Import the IV
        %ivHash = $self->getEditHash_hashIV($iv);

        # Get a list of the keys in %ivHash, sorted in order of each key's value
        @sortedList = sort {

            if ($ivHash{$a} == $ivHash{$b}) {

                # Values are equal, so sort by key
                return (lc($a) cmp lc($b));

            } else {

                # Sort by value
                return $ivHash{$a} <=> $ivHash{$b};
            }

        } (keys %ivHash);

        # Compile the simple list data
        foreach my $key (@sortedList) {

            push (@dataList,
                $key,
                $ivHash{$key},
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub timeTab {

        # Clock tab
        #
        # 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 . '->clockTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Time');

        # Add tabs to the inner notebook
        $self->time1Tab($innerNotebook);
        $self->time2Tab($innerNotebook);
        $self->time3Tab($innerNotebook);
        $self->time4Tab($innerNotebook);

        return 1;
    }

    sub time1Tab {

        # Time1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (%timeHash, %timePluralHash);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Time units'],
        );

        # Make a local copy of the dictionary's time hashes
        %timeHash = $self->getEditHash_hashIV('timeHash');
        %timePluralHash = $self->getEditHash_hashIV('timePluralHash');

        # Time units
        $self->addLabel($grid, '<b>Time units</b>',
            0, 4, 0, 1);
        $self->addLabel($grid, '<i>Singular</i>',
            4, 8, 0, 1);
        $self->addLabel($grid, '<i>Plural</i>',
            8, 12, 0, 1);

        $self->addLabel($grid, 'Seconds',
            1, 4, 1, 2);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'second',
            4, 8, 1, 2);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'second',
            8, 12, 1, 2);

        $self->addLabel($grid, 'Minutes',
            1, 4, 2, 3);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'minute',
            4, 8, 2, 3);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'minute',
            8, 12, 2, 3);

        $self->addLabel($grid, 'Hours',
            1, 4, 3, 4);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'hour',
            4, 8, 3, 4);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'hour',
            8, 12, 3, 4);

        $self->addLabel($grid, 'Days',
            1, 4, 4, 5);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'day',
            4, 8, 4, 5);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'day',
            8, 12, 4, 5);

        $self->addLabel($grid, 'Weeks',
            1, 4, 5, 6);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'week',
            4, 8, 5, 6);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'week',
            8, 12, 5, 6);

        $self->addLabel($grid, 'Months',
            1, 4, 6, 7);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'month',
            4, 8, 6, 7);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'month',
            8, 12, 6, 7);

        $self->addLabel($grid, 'Years',
            1, 4, 7, 8);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'year',
            4, 8, 7, 8);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'year',
            8, 12, 7, 8);

        $self->addLabel($grid, 'Decades',
            1, 4, 8, 9);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'decade',
            4, 8, 8, 9);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'decade',
            8, 12, 8, 9);

        $self->addLabel($grid, 'Centuries',
            1, 4, 9, 10);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'century',
            4, 8, 9, 10);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'century',
            8, 12, 9, 10);

        $self->addLabel($grid, 'Millennia',
            1, 4, 10, 11);
        $self->time1Tab_addEntryWithIcon($grid, 'timeHash', 'millennium',
            4, 8, 10, 11);
        $self->time1Tab_addEntryWithIcon($grid, 'timePluralHash', 'millennium',
            8, 12, 10, 11);

        # Tab complete
        return 1;
    }

    sub time1Tab_addEntryWithIcon {

        # Adds a Gtk3::Entry at the specified position on the tab, containing a stock icon to show
        #   whether the current contents of the entry is allowed
        # The stock icons used are 'gtk-yes' (for an acceptable value) and 'gtk-no' (for a forbidden
        #   value)
        # This is a customised version of the ->addEntryWithIcon method found in
        #   GA::Generic::EditWin
        #
        # Expected arguments
        #   $grid       - The Gtk3::Grid object in which the entry boxes are placed
        #   $iv         - A string naming the hash IV set (either 'timeHash' or 'timePluralHash')
        #   $key        - The key being set in the hash IV
        #   $leftAttach, $rightAttach, $topAttach, $bottomAttach
        #               - The position of the entry box in the grid
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns the Gtk3::Entry created

        my (
            $self, $grid, $iv, $key, $leftAttach, $rightAttach, $topAttach, $bottomAttach, $check
        ) = @_;

        # Local variables
        my %ivHash;

        # Check for improper arguments
        if (
            ! defined $grid || ! defined $iv || ! defined $key || ! defined $leftAttach
            || ! defined $rightAttach || ! defined $topAttach || ! defined $bottomAttach
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->time1Tab_addEntryWithIcon',
                @_,
            );
        }

        # Import the hash being edited
        %ivHash = $self->getEditHash_hashIV($iv);

        # Create the entry
        my $entry = Gtk3::Entry->new();

        # Display the existing value of the IV
        if (defined $self->editObj->$iv) {

            $entry->set_text($ivHash{$key});

            if ($ivHash{$key}) {
                $entry->set_icon_from_stock('secondary', 'gtk-yes');
            } else {
                $entry->set_icon_from_stock('secondary', 'gtk-no');
            }

        } else {

            # We need to set the icon for an empty box
            $entry->set_icon_from_stock('secondary', 'gtk-no');
        }

        # Customise the entry
        $entry->signal_connect('changed' => sub {

            my $value = $entry->get_text();

            $self->modifyEditHash_hashIV($iv, $key, $value);

            if ($value) {
                $entry->set_icon_from_stock('secondary', 'gtk-yes');
            } else {
                $entry->set_icon_from_stock('secondary', 'gtk-no');
            }
        });

        # Add the entry to the grid directly
        $grid->attach(
            $entry,
            $leftAttach,
            $topAttach,
            ($rightAttach - $leftAttach),
            ($bottomAttach - $topAttach),
        );

        return $entry;
    }

    sub time2Tab {

        # Time2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @comboList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Clock patterns - time of day'],
        );

        # Clock patterns - time of day
        $self->addLabel($grid, '<b>Clock patterns - time of day</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Use a value of 0 for a.m., 1 for p.m.</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockDayHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Value:',
            7, 10, 8, 9);
        @comboList = (0, 1);
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            10, 12, 8, 9);

        # Add buttons that resemble those from GA::Generic::EditWin->addSimpleListButtons_hashIV
        my $button = $self->addButton($grid, 'Add pattern', 'Add a new clock pattern', undef,
            1, 3, 9, 10);
        $button->signal_connect('clicked' => sub {

            my ($pattern, $value);

            if ($self->checkEntryIcon($entry)) {

                $pattern = $entry->get_text();
                $value = $combo->get_active_text();

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('clockDayHash', $pattern, $value);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    'clockDayHash',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        my $button2 = $self->addButton($grid,
            'Delete pattern', 'Delete the selected clock pattern', undef,
            6, 8, 9, 10);
        $button2->signal_connect('clicked' => sub {

            my ($term) = $self->getSimpleListData($slWidget, 0);
            if (defined $term) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('clockDayHash', $term, undef, TRUE);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    'clockDayHash',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        my $button3 = $self->addButton($grid,
            'Reset', 'Reset the hash which stores the clock patterns', undef,
            8, 10, 9, 10);
        $button3->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'clockDayHash');

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockDayHash');
            $self->resetEntryBoxes($entry);
        });

        my $button4 = $self->addButton($grid,
            'Clear', 'Clear the hash which stores the clock patterns', undef,
            10, 12, 9, 10);
        $button4->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'clockDayHash', {});

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockDayHash');
            $self->resetEntryBoxes($entry);
        });

        # Tab complete
        return 1;
    }

    sub time3Tab {

        # Time3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Clock patterns - hours'],
        );

        # Clock patterns - hours
        $self->addLabel($grid, '<b>Clock patterns - hours</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Use a value of 1-12, but use 0 for midnight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockHourHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Value',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add buttons that resemble those from GA::Generic::EditWin->addSimpleListButtons_hashIV
        my $button = $self->addButton($grid, 'Add pattern', 'Add a new clock pattern', undef,
            1, 3, 9, 10);
        $button->signal_connect('clicked' => sub {

            my ($term, $value);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $term = $entry->get_text();
                $value = $entry2->get_text();

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('clockHourHash', $term, $value);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    'clockHourHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        my $button2 = $self->addButton($grid,
            'Delete pattern', 'Delete the selected clock pattern', undef,
            6, 8, 9, 10);
        $button2->signal_connect('clicked' => sub {

            my ($term) = $self->getSimpleListData($slWidget, 0);
            if (defined $term) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('clockHourHash', $term, undef, TRUE);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    'clockHourHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        my $button3 = $self->addButton($grid,
            'Reset', 'Reset the hash which stores the clock patterns', undef,
            8, 10, 9, 10);
        $button3->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'clockHourHash');

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockHourHash');
            $self->resetEntryBoxes($entry, $entry2);
        });

        my $button4 = $self->addButton($grid,
            'Clear', 'Clear the hash which stores the clock patterns', undef,
            10, 12, 9, 10);
        $button4->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'clockHourHash', {});

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockHourHash');
            $self->resetEntryBoxes($entry, $entry2);
        });

        # Tab complete
        return 1;
    }

    sub time4Tab {

        # Time4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Clock patterns - minutes'],
        );

        # Clock patterns - minutes
        $self->addLabel($grid, '<b>Clock patterns - minutes</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Use positive values for minutes past, negative for minutes to (0 for hours)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockMinuteHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Value',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add buttons that resemble those created by
        #   GA::Generic::EditWin->addSimpleListButtons_hashIV
        my $button = $self->addButton($grid, 'Add pattern', 'Add a new clock pattern', undef,
            1, 3, 9, 10);
        $button->signal_connect('clicked' => sub {

            my ($term, $value);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $term = $entry->get_text();
                $value = $entry2->get_text();

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('clockMinuteHash', $term, $value);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    'clockMinuteHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        my $button2 = $self->addButton($grid,
            'Delete pattern', 'Delete the selected clock pattern', undef,
            6, 8, 9, 10);
        $button2->signal_connect('clicked' => sub {

            my ($term) = $self->getSimpleListData($slWidget, 0);
            if (defined $term) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('clockMinuteHash', $term, undef, TRUE);

                # Refresh the simple list and reset entry boxes
                $self->numbersTab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    'clockMinuteHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        my $button3 = $self->addButton($grid,
            'Reset', 'Reset the hash which stores the clock patterns', undef,
            8, 10, 9, 10);
        $button3->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'clockMinuteHash');

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockMinuteHash');
            $self->resetEntryBoxes($entry, $entry2);
        });

        my $button4 = $self->addButton($grid,
            'Clear', 'Clear the hash which stores the clock patterns', undef,
            10, 12, 9, 10);
        $button4->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'clockMinuteHash', {});

            # Refresh the simple list and reset entry boxes
            $self->numbersTab_refreshList($slWidget, (scalar @columnList / 2), 'clockMinuteHash');
            $self->resetEntryBoxes($entry, $entry2);
        });

        # Tab complete
        return 1;
    }

    sub nounsTab {

        # Nouns tab
        #
        # 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 . '->nounsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'N_ouns');

        # Add tabs to the inner notebook
        $self->nouns1Tab($innerNotebook);
        $self->nouns2Tab($innerNotebook);
        $self->nouns3Tab($innerNotebook);
        $self->nouns4Tab($innerNotebook);
        $self->nouns5Tab($innerNotebook);
        $self->nouns6Tab($innerNotebook);
        $self->nouns7Tab($innerNotebook);
        $self->nouns8Tab($innerNotebook);
        $self->nouns9Tab($innerNotebook);
        $self->nouns10Tab($innerNotebook);

        return 1;
    }

    sub nouns1Tab {

        # Nouns1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Sentients'],
        );

        # Sentients
        $self->addLabel($grid, '<b>Sentients</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of words for sentient beings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'sentientHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('sentient');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'sentientHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('sentientHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'sentientHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns2Tab {

        # Nouns2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Creatures'],
        );

        # Creatures
        $self->addLabel($grid, '<b>Creatures</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of words for non-sentient beings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'creatureHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('creature');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'creatureHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('creatureHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'creatureHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns3Tab {

        # Nouns3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Portable types', 'Decoration types'],
        );

        # Portable types
        $self->addLabel($grid, '<b>Portable types</b>',
            0, 10, 0, 1);
        $self->addLabel($grid, '<i>Objects which can normally be picked up</i>',
            1, 10, 1, 2);
        my $textView = $self->addTextView($grid, 'portableTypeList', TRUE,
            1, 12, 2, 5,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
        );
        my $buffer = $textView->get_buffer();

        my $button = Gtk3::Button->new('Use default list');
        $button->signal_connect('clicked' => sub {

            my @list = $self->editObj->constPortableTypeList;

            $self->ivAdd('editHash', \@list);
            $buffer->set_text(join("\n", $self->editObj->ivPeek('constPortableTypeList')));
        });
        $button->set_tooltip_text('Use the default list of Portable types');
        $grid->attach($button, 10, 0, 2, 2);

        # Decoration types
        $self->addLabel($grid, '<b>Decoration types</b>',
            0, 10, 5, 6);
        $self->addLabel($grid, '<i>Objects which can\'t normally be picked up</i>',
            1, 10, 6, 7);
        my $textView2 = $self->addTextView($grid, 'decorationTypeList', TRUE,
            1, 12, 7, 10,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
        );
        my $buffer2 = $textView2->get_buffer();

        my $button2 = Gtk3::Button->new('Use default list');
        $button2->signal_connect('clicked' => sub {

            my @list = $self->editObj->constDecorationTypeList;

            $self->ivAdd('editHash', \@list);
            $buffer2->set_text(join("\n", $self->editObj->ivPeek('constDecorationTypeList')));
        });
        $button2->set_tooltip_text('Use the default list of Decoration types');
        $grid->attach($button2, 10, 5, 2, 2);

        # Tab complete
        return 1;
    }

    sub nouns4Tab {

        # Nouns4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @comboList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 4',
            ['Portables'],
        );

        # Portables
        $self->addLabel($grid, '<b>Portables</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of portable words (objects which can usually be picked up)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'portableTypeHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);

        @comboList = $self->getEditHash_listIV('portableTypeList');
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'portableTypeHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $comboBox->get_active_text();

            if ($self->checkEntryIcon($entry) && $type) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('portableTypeHash', $word, $type);
                # (We have a second hash to update)
                $self->modifyEditHash_hashIV('portableHash', $word, 'portable');

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'portableTypeHash',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns5Tab {

        # Nouns5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @comboList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Decorations'],
        );

        # Decorations
        $self->addLabel($grid, '<b>Decorations</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of decoration words (objects which can\'t usually be picked up)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'decorationTypeHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);

        @comboList = $self->getEditHash_listIV('decorationTypeList');
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'decorationTypeHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $comboBox->get_active_text();

            if ($self->checkEntryIcon($entry) && $type) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('decorationTypeHash', $word, $type);
                # (We have a second hash to update)
                $self->modifyEditHash_hashIV('decorationHash', $word, 'decoration');

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'decorationTypeHash',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns6Tab {

        # Nouns6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Guilds'],
        );

        # Guilds
        $self->addLabel($grid, '<b>Guilds</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of guild words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'guildHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('guild');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'guildHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('guildHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'guildHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns7Tab {

        # Nouns7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Races'],
        );

        # Races
        $self->addLabel($grid, '<b>Races</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of race words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'raceHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('race');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'raceHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('raceHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'raceHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns8Tab {

        # Nouns8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Weapons'],
        );

        # Weapons
        $self->addLabel($grid, '<b>Weapons</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of weapon words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'weaponHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('weapon');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'weaponHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('weaponHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'weaponHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns9Tab {

        # Nouns9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _9',
            ['Armour'],
        );

        # Armour
        $self->addLabel($grid, '<b>Armour</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of armour words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'armourHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('armour');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'armourHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('armourHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'armourHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub nouns10Tab {

        # Nouns10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 1_0',
            ['Garments'],
        );

        # Garments
        $self->addLabel($grid, '<b>Garments</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of garment words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'garmentHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, 0,
            10, 12, 8, 9);
        $entry2->set_text('garment');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'garmentHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('garmentHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'garmentHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub pluralsTab {

        # Plurals tab
        #
        # 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 . '->pluralsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Plurals');

        # Add tabs to the inner notebook
        $self->plurals1Tab($innerNotebook);
        $self->plurals2Tab($innerNotebook);
        $self->plurals3Tab($innerNotebook);

        return 1;
    }

    sub plurals1Tab {

        # Plurals1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Plural ending patterns'],
        );

        # Plural ending patterns
        $self->addLabel($grid, '<b>Plural ending patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Plural patterns and their equivalent singular endings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Plural pattern', 'text',
            'Singular ending', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pluralEndingHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Plural pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Singular ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 0, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'pluralEndingHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $singular);

            $pattern = $entry->get_text();
            $singular = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('pluralEndingHash', $pattern, $singular);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pluralEndingHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub plurals2Tab {

        # Plurals2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Singular ending patterns'],
        );

        # Singular ending patterns
        $self->addLabel($grid, '<b>Singular ending patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Singular patterns and their equivalent plural endings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Singular pattern', 'text',
            'Plural ending', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'reversePluralEndingHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Singular pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 0, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Plural ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'reversePluralEndingHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $singular);

            $pattern = $entry->get_text();
            $singular = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('reversePluralEndingHash', $pattern, $singular);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'reversePluralEndingHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub plurals3Tab {

        # Plurals3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Plural nouns'],
        );

        # Plural nouns
        $self->addLabel($grid, '<b>Plural nouns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of nouns that are exceptions to the usual rules</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Singular noun', 'text',
            'Plural noun', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pluralNounHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Singular noun',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Plural noun',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'pluralNounHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($singular, $plural);

            $singular = $entry->get_text();
            $plural = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('pluralNounHash', $singular, $plural);
                # (We have a second hash to update)
                $self->modifyEditHash_hashIV('reversePluralNounHash', $plural, $singular);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pluralNounHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub adjectivesTab {

        # Adjectives tab
        #
        # 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 . '->adjectivesTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Adj_ectives');

        # Add tabs to the inner notebook
        $self->adjectives1Tab($innerNotebook);
        $self->adjectives2Tab($innerNotebook);
        $self->adjectives3Tab($innerNotebook);
        $self->adjectives4Tab($innerNotebook);

        return 1;
    }

    sub adjectives1Tab {

        # Adjectives1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Adjectives'],
        );

        # Adjectives
        $self->addLabel($grid, '<b>Adjectives</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of adjective words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'adjHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('adj');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'adjHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($word, $type);

            $word = $entry->get_text();
            $type = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('adjHash', $word, $type);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'adjHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub adjectives2Tab {

        # Adjectives2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Declined adjective patterns'],
        );

        # Declined adjective patterns
        $self->addLabel($grid, '<b>Declined adjective patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Declined adjective endings and their equivalent undeclined endings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Declined ending pattern', 'text',
            'Undeclined ending', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'adjEndingHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Declined ending pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Undeclined ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'adjEndingHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($declined, $undeclined);

            $declined = $entry->get_text();
            $undeclined = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('adjEndingHash', $declined, $undeclined);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'adjEndingHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub adjectives3Tab {

        # Adjectives3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Undeclined adjective patterns'],
        );

        # Undeclined adjective patterns
        $self->addLabel($grid, '<b>Undeclined adjective patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Undeclined adjective endings and their equivalent declined endings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Undeclined ending pattern', 'text',
            'Declined ending', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'reverseAdjEndingHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Undeclined ending pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Declined ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'reverseAdjEndingHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($declined, $undeclined);

            $declined = $entry->get_text();
            $undeclined = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('reverseAdjEndingHash', $declined, $undeclined);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'reverseAdjEndingHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub adjectives4Tab {

        # Adjectives4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Declined adjectives'],
        );

        # Declined adjectives
        $self->addLabel($grid, '<b>Declined adjectives</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of adjectives that are exceptions to the usual declension rules</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Undeclined form', 'text',
            'Declined form', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'declinedAdjHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Undeclined form',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Declined form',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'declinedAdjHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($declined, $undeclined);

            $declined = $entry->get_text();
            $undeclined = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('declinedAdjHash', $declined, $undeclined);
                # (We have a second hash to update)
                $self->modifyEditHash_hashIV(
                    'reverseDeclinedAdjHash',
                    $undeclined,
                    $declined,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'declinedAdjHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub pseudoTab {

        # Pseudo tab
        #
        # 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 . '->pseudoTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'P_seudo');

        # Add tabs to the inner notebook
        $self->pseudo1Tab($innerNotebook);
        $self->pseudo2Tab($innerNotebook);
        $self->pseudo3Tab($innerNotebook);

        return 1;
    }

    sub pseudo1Tab {

        # Pseudo1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Pseudo nouns'],
        );

        # Pseudo nouns
        $self->addLabel($grid, '<b>Pseudo nouns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Groups of words which represent a single noun</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pseudo noun', 'text',
            'Replacement noun', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pseudoNounHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pseudo noun (pattern)',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 12, 8, 9);

        $self->addLabel($grid, 'Replacement noun',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'pseudoNounHash',
            10,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pseudo, $replacement);

            $pseudo = $entry->get_text();
            $replacement = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('pseudoNounHash', $pseudo, $replacement);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pseudoNounHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub pseudo2Tab {

        # Pseudo2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Pseudo objects'],
        );

        # Pseudo objects
        $self->addLabel($grid, '<b>Pseudo objects</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Groups of words which represent a single object</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pseudo object', 'text',
            'Replacement object', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pseudoObjHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pseudo object (pattern)',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 12, 8, 9);

        $self->addLabel($grid, 'Replacement object (if any)',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 0, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'pseudoObjHash',
            10,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pseudo, $replacement);

            $pseudo = $entry->get_text();
            $replacement = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('pseudoObjHash', $pseudo, $replacement);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pseudoObjHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub pseudo3Tab {

        # Pseudo3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Pseudo adjectives'],
        );

        # Pseudo adjectives
        $self->addLabel($grid, '<b>Pseudo adjectives</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Groups of words which represent a single adjective</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pseudo adjective', 'text',
            'Replacement adjective', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pseudoAdjHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pseudo adjective (pattern)',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            4, 12, 8, 9);

        $self->addLabel($grid, 'Replacement adjective (if any)',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 0, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'pseudoAdjHash',
            10,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pseudo, $replacement);

            $pseudo = $entry->get_text();
            $replacement = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('pseudoAdjHash', $pseudo, $replacement);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'pseudoAdjHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub contentsTab {

        # Contents tab
        #
        # 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 . '->contentsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Contents');

        # Add tabs to the inner notebook
        $self->contents1Tab($innerNotebook);
        $self->contents2Tab($innerNotebook);
        $self->contents3Tab($innerNotebook);

        return 1;
    }

    sub contents1Tab {

        # Contents1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Death words', 'Ignore words'],
        );

        # Death words
        $self->addLabel($grid, '<b>Death words</b>',
            0, 6, 0, 2);
        $self->addLabel($grid, '<i>List of words which mark an object as dead</i>',
            1, 6, 2, 4);
        my ($textView) = $self->addTextView($grid, undef, TRUE,
            1, 6, 4, 6,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
        );
        my $buffer = $textView->get_buffer();
        $buffer->set_text(join("\n", $self->editObj->ivKeys('deathWordHash')));
        $buffer->signal_connect('changed' => sub {

            # (The 'edit' window presents this IV as a list, but the dictionary object stores it as
            #   a hash)

            my (
                $text,
                @list, @finalList,
                %hash,
            );

            $text = $axmud::CLIENT->desktopObj->bufferGetText($buffer);

            # Split the contents of the textview into a list of lines, separated by newline
            #   characters
            @list = split("\n", $text);
            # Remove any empty lines and leading/trailing whitespace
            foreach my $line (@list) {

                if ($line) {

                    $line =~ s/^\s*//;      # Remove leading whitespace
                    $line =~ s/\s*$//;      # Remove trailing whitepsace
                    push (@finalList, $line);
                }

                # From @finalList, compile the hash
                foreach my $word (@finalList) {

                    $hash{$word} = undef;
                }

                # Set the IV using the hash
                $self->ivAdd('editHash', 'deathWordHash', \%hash);
            }
        });

        # Ignore words
        $self->addLabel($grid, '<b>Ignore words</b>',
            0, 6, 6, 8);
        $self->addLabel($grid,
            '<i>List of words that should be ignored (never added to the unknown words list)</i>',
            1, 6, 8, 10);
        my $textView2 = $self->addTextView($grid, undef, TRUE,
            1, 6, 10, 12,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
        );
        my $buffer2 = $textView2->get_buffer();
        $buffer2->set_text(join("\n", $self->editObj->ivKeys('ignoreWordHash')));
        $buffer2->signal_connect('changed' => sub {

            # (The 'edit' window presents this IV as a list, but the dictionary object stores it as
            #   a hash)

            my (
                $text,
                @list, @finalList,
                %hash,
            );

            $text = $axmud::CLIENT->desktopObj->bufferGetText($buffer);

            # Split the contents of the textview into a list of lines, separated by newline
            #   characters
            @list = split("\n", $text);
            # Remove any empty lines and leading/trailing whitespace
            foreach my $line (@list) {

                if ($line) {

                    $line =~ s/^\s*//;      # Remove leading whitespace
                    $line =~ s/\s*$//;      # Remove trailing whitepsace
                    push (@finalList, $line);
                }

                # From @finalList, compile the hash
                foreach my $word (@finalList) {

                    $hash{$word} = 'ignore_word';
                }

                # Set the IV using the hash
                $self->ivAdd('editHash', 'ignoreWordHash', \%hash);
            }
        });

        # Tab complete
        return 1;
    }

    sub contents2Tab {

        # Contents2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $listRef,
            @columnList, @comboList,
            %ivHash,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Unknown words'],
        );

        # Unknown words
        $self->addLabel($grid, '<b>Unknown words</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Gathered words which aren\'t yet defined in this dictionary <b>(use the \'Save\''
            . ' button before editing other other tabs)</b></i>',
            1, 12, 1, 2);

        # Add a simple list, and make it scrollable
        my $frame = Gtk3::Frame->new(undef);
        $frame->set_border_width(0);
        $grid->attach($frame, 1, 2, 5, 12);

        my $scroller = Gtk3::ScrolledWindow->new;
        $frame->add($scroller);
        $scroller->set_policy('automatic', 'automatic');
        $scroller->set_vexpand(TRUE);

        @columnList = ('Unknown word', 'text');
        my $slWidget = Games::Axmud::Obj::SimpleList->new(@columnList);
        $scroller->add($slWidget);
        $slWidget->get_selection->set_mode('multiple');

        # Initialise the list. Import the IV
        %ivHash = $self->getEditHash_hashIV('unknownWordHash');
        @{$slWidget->{data}} = sort {lc($a) cmp lc($b)} (keys %ivHash);

        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Guild', 'Selected words are guilds', 'addword -g ', undef,
            6, 8, 2, 3);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Race', 'Selected words are races', 'addword -r ', undef,
            8, 10, 2, 3);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Weapon', 'Selected words are weapons', 'addword -w ', undef,
            10, 12, 2, 3);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Armour', 'Selected words are armours', 'addword -a ', undef,
            6, 8, 3, 4);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Garment', 'Selected words are garments', 'addword -e ', undef,
            8, 10, 3, 4);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Sentient', 'Selected words are sentient beings', 'addword -s ', undef,
            10, 12, 3, 4);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Creature', 'Selected words are non-sentient beings', 'addword -k ', undef,
            6, 8, 5, 6);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Adjective', 'Selected words are adjectives', 'addword -j ', undef,
            8, 10, 5, 6);
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Ignore', 'Selected words can be ignored in future', 'addword -i ', undef,
            10, 12, 5, 6);

        # Portables
        if ($self->ivExists('editHash', 'portableTypeList')) {

            $listRef = $self->ivShow('editHash', 'portableTypeList');
            @comboList = @$listRef;

        } else {

            @comboList = $self->editObj->portableTypeList;
        }

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 9, 6, 7);

        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Portable', 'Selected words are portables', 'addword -p ', $comboBox,
            9, 12, 6, 7);

        # Decorations
        if ($self->ivExists('editHash', 'decorationTypeList')) {

            $listRef = $self->ivShow('editHash', 'decorationTypeList');
            @comboList = @$listRef;

        } else {

            @comboList = $self->editObj->decorationTypeList;
        }

        my $comboBox2 = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 9, 7, 8);

        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Decoration', 'Selected words are decorations', 'addword -d ', $comboBox2,
            9, 12, 7, 8);

        # Delete word
        $self->contents2Tab_addButtons($grid,
            $slWidget, 'Delete word(s)', 'Selected words are deleted from the list', undef, undef,
            9, 12, 8, 9);

        # Widgets can't be manipulated when this isn't a current dictionary (buttons are
        #   desensitised in the calls to $self->contents2Tab_addButtons)
        if (! $self->currentFlag) {

            $comboBox->set_sensitive(FALSE);
            $comboBox2->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub contents2Tab_addButtons {

        # Called by $self->words2Tab
        # Adds a single button that, when clicked, runs the client command ';addword' on the
        #   selected item(s) in the GA::Obj::SimpleList displayed in this tab
        #
        # Expected arguments
        #   $grid       - The Gtk3::Grid for the tab
        #   $slWidget   - The GA::Obj::SimpleList displayed
        #   $title      - The button's title
        #   $tooltips   - Tooltips for the button
        #   $cmd        - The text of the command to send, e.g. 'addword -r' to which the selected
        #                   items are appended (e.g. 'addword -r troll'). If 'undef', no command is
        #                   sent (so the word is simply removed from the list)
        #   $widget     - The Gtk::ComboBox linked to this button ('undef' if not used)
        #   $leftAttach, $rightAttach, $topAttach, $bottomAttach
        #               - The position of the button in the tab's grid
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise the Gtk3::Button created created

        my (
            $self, $grid, $slWidget, $title, $tooltips, $cmd, $widget, $leftAttach, $rightAttach,
            $topAttach, $bottomAttach, $check
        ) = @_;

        # Local variables
        my (
            @indexList,
            %ivHash,
        );

        # Check for improper arguments
        if (
            ! defined $grid || ! defined $slWidget|| ! defined $title || ! defined $tooltips
            || ! defined $leftAttach || ! defined $rightAttach || ! defined $topAttach
            || ! defined $bottomAttach || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->contents2Tab_addButtons',
                @_,
            );
        }

        # Create the button
        my $button = $self->addButton($grid, $title, $tooltips, undef,
            $leftAttach, $rightAttach, $topAttach, $bottomAttach,
            TRUE,               # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($arg, $arg2);

            # Get the current hash of unknown words by importing the IV
            %ivHash = $self->getEditHash_hashIV('unknownWordHash');

            # Get the second argument to the 'addword' command, if any
            if (! defined $widget) {

                $arg2 = '';     # Will be appended to $arg

            } elsif ($widget->get_active_text()) {

                $arg2 = ' ' . $widget->get_active_text();
            }

            if (defined $arg2) {

                # (Allow multiple selected words; deal with each one in turn)
                @indexList = $slWidget->get_selected_indices();
                foreach my $index (@indexList) {

                    $arg = ${$slWidget->{data}}[$index][0];
                    delete $ivHash{$arg};

                    if ($cmd) {

                        $self->session->pseudoCmd($cmd . $arg . $arg2, $self->pseudoCmdMode);
                    }
                }

                @{$slWidget->{data}} = sort {lc($a) cmp lc($b)} (keys %ivHash);
                $self->ivAdd('editHash', 'unknownWordHash', \%ivHash);
            }
        });

        # Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
        }

        return $button;
    }

    sub contents3Tab {

        # Contents3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $unknownFlag,
            @columnList,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Contents lines'],
        );

        # Contents lines
        $self->addLabel($grid, '<b>Contents lines</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Contents lines gathered by the Locator task <b>(use the \'Save\' button before'
            . ' editing other tabs)</b></i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Contents line', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->contents3Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing widgets
        my $button = $self->addButton($grid,
            'Use selected', 'Copy the selected contents line into the entry box below', undef,
            1, 3, 8, 9);
        my $button2 = $self->addButton($grid,
            'Delete selected', 'Delete the selected contents line', undef,
            8, 10, 8, 9);
        my $button3 = $self->addButton($grid,
            'Delete all', 'Empty the hash which stores contents lines', undef,
            10, 12, 8, 9);

        $self->addLabel($grid, 'Pattern',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 12, 9, 10);
        $self->addLabel($grid, 'Replacement',
            1, 3, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 10, 11);

        # ->signal_connects from above
        $button->signal_connect('clicked' => sub {

            my ($key) = $self->getSimpleListData($slWidget, 0);
            if (defined $key) {

                $entry->set_text($key);
                $entry2->set_text('');
            }
        });

        $button2->signal_connect('clicked' => sub {

            my ($key) = $self->getSimpleListData($slWidget, 0);
            if (defined $key) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('contentsLinesHash', $key, undef, TRUE);

                # Refresh the simple list and reset entry boxes
                $self->contents3Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        $button3->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'contentsLinesHash', {});

            # Refresh the simple list and reset entry boxes
            $self->contents3Tab_refreshList($slWidget, (scalar @columnList / 2));
            $self->resetEntryBoxes($entry, $entry2);
        });

        # More editing widgets
        my $button4 = $self->addButton($grid,
            'Add pseudo-noun', 'Add the pattern and replacement as a pseudo-noun', undef,
            6, 9, 10, 11,
            TRUE,               # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my ($pattern, $replacement);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $pattern = $entry->get_text();
                $replacement = $entry2->get_text();

                $self->session->pseudoCmd(
                    'addword -x <' . $replacement . '> <' . $pattern . '>',
                    $self->pseudoCmdMode,
                );

                if ($unknownFlag) {

                    $self->session->pseudoCmd(
                        'addword -u ' . join(' ', split(/\s+/, $replacement)),
                        $self->pseudoCmdMode,
                    );
                }

                # (Reset the replacement, but not the pattern)
                $entry2->set_text('');
            }
        });

        my $button5 = $self->addButton($grid,
            'Add pseudo-obj', 'Add the pattern and replacement as a pseudo-object', undef,
            9, 12, 10, 11,
            TRUE,               # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            my ($pattern, $replacement);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $pattern = $entry->get_text();
                $replacement = $entry2->get_text();

                $self->session->pseudoCmd(
                    'addword -v <' . $replacement . '> <' . $pattern . '>',
                    $self->pseudoCmdMode,
                );

                if ($unknownFlag) {

                    $self->session->pseudoCmd(
                        'addword -u ' . join(' ', split(/\s+/, $replacement)),
                        $self->pseudoCmdMode,
                    );
                }

                # (Reset the replacement, but not the pattern)
                $entry2->set_text('');
            }
        });

        my $checkButton = $self->addCheckButton(
            $grid, 'Also add the replacement as unknown word(s)', undef, TRUE,
            1, 6, 11, 12);
        $unknownFlag = FALSE;       # Button starts deselected
        $checkButton->signal_connect('toggled' => sub {

            if ($checkButton->get_active()) {
                $unknownFlag = TRUE;
            } else {
                $unknownFlag = FALSE;
            }
        });

        my $button6 = $self->addButton($grid,
            'Add pseudo-adj', 'Add the pattern and replacement as a pseudo-adjective', undef,
            6, 9, 11, 12,
            TRUE,               # Irreversible
        );
        $button6->signal_connect('clicked' => sub {

            my ($pattern, $replacement);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $pattern = $entry->get_text();
                $replacement = $entry2->get_text();

                $self->session->pseudoCmd(
                    'addword -y <' . $replacement . '> <' . $pattern . '>',
                    $self->pseudoCmdMode,
                );

                if ($unknownFlag) {

                    $self->session->pseudoCmd(
                        'addword -u ' . join(' ', split(/\s+/, $replacement)),
                        $self->pseudoCmdMode,
                    );
                }

                # (Reset the replacement, but not the pattern)
                $entry2->set_text('');
            }
        });

        # Tab complete
        return 1;
    }

    sub contents3Tab_refreshList {

        # Called by $self->contentsTab to refresh the GA::Obj::SimpleList, displaying only keys
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @sortedList, @dataList,
            %ivHash,
        );

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

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

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('contentsLinesHash');

        # Compile the simple list data
        push (@dataList, (sort {lc($a) cmp lc($b)} (keys %ivHash)));

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub directionsTab {

        # Directions tab
        #
        # 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 . '->directionsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Directions');

        # Add tabs to the inner notebook
        $self->directions1Tab($innerNotebook);
        $self->directions2Tab($innerNotebook);
        $self->directions3Tab($innerNotebook);
        $self->directions4Tab($innerNotebook);
        $self->directions5Tab($innerNotebook);

        return 1;
    }

    sub directions1Tab {

        # Directions1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $showAllFlag,
            @columnList,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Primary directions'],
        );

        # Primary directions
        $self->addLabel($grid, '<b>Primary directions</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of the standard compass directions, plus up/down</i>',
            1, 6, 1, 2);

        my $checkButton = $self->addCheckButton(
            $grid, 'Show all intercardinal directions', undef, TRUE,
            6, 12, 1, 2);
        # (->signal_connect appears below)

        # Create the simple list
        @columnList = (
            'Standard', 'text',
            'Custom', 'text',
            'Abbrev\'n', 'text',
            'Opposite dirs', 'text',
            'Opp abbrev\'d dirs', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 7);

        # Initialise the list
        $self->directions1Tab_refreshList($slWidget, (scalar @columnList / 2), $showAllFlag);

        # Entry boxes
        $self->addLabel($grid, 'Map direction',
            1, 4, 8, 9);
        my $entry = $self->addEntry($grid, undef, FALSE,
            4, 6, 8, 9);

        $self->addLabel($grid, 'Direction',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            9, 12, 8, 9);

        $self->addLabel($grid, 'Abbreviation',
            1, 4, 9, 10);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            4, 6, 9, 10);

        $self->addLabel($grid, 'Opposite dirs',
            6, 9, 9, 10);
        my $entry4 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            9, 12, 9, 10);

        $self->addLabel($grid, 'Opposite abbrev dirs',
            1, 4, 10, 11);
        my $entry5 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            4, 6, 10, 11);

        # 'Edit direction' button
        my $button = $self->addButton($grid,
            'Edit direction', 'Edit the selected direction', undef,
            1, 3, 7, 8);
        $button->signal_connect('clicked' => sub {

            my ($standard, $dir, $abbrev, $opp, $oppAbbrev)
                = $self->getSimpleListData($slWidget, 0, 1, 2, 3, 4);

            if ($standard) {

                # Sensitise the widgets (but not $entry, which is always desensitised)
                $self->sensitiseWidgets($entry2, $entry3, $entry4, $entry5);

                # Fill the entry boxes with the selected direction's data
                $entry->set_text($standard);
                $entry2->set_text($dir);
                $entry3->set_text($abbrev);
                $entry4->set_text($opp);
                $entry5->set_text($oppAbbrev);
            }
        });

        # 'Use changes' button
        my $button2 = $self->addButton($grid,
            'Use changes', 'Use the modified directions', undef,
            10, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my (
                $standard, $dir, $abbrev, $opp, $oppAbbrev, $msg, $hashRef,
                %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash, %secDirHash, %secAbbrevHash,
                %relDirHash, %relAbbrevHash,
                %combDirHash,
            );

            if ($self->checkEntryIcon($entry2, $entry3, $entry4, $entry5)) {

                $standard = $entry->get_text();
                # (Definitely need to remove any leading/trailing whitespace, as well as replace
                #   two or more whitespace characters in the middle with a single whitespace
                #   character)
                $dir = $axmud::CLIENT->trimWhitespace($entry2->get_text(), TRUE);
                $abbrev = $axmud::CLIENT->trimWhitespace($entry3->get_text(), TRUE);
                $opp = $axmud::CLIENT->trimWhitespace($entry4->get_text(), TRUE);
                $oppAbbrev = $axmud::CLIENT->trimWhitespace($entry5->get_text(), TRUE);

                # Need to check that the user hasn't added an existing custom primary or recognised
                #   secondary direction
                # Import data from the affected IVs
                if ($self->ivExists('editHash', 'primaryDirHash')) {

                    # (If any primary dir IVs have been changed, then they are ALL in ->editHash)
                    $hashRef = $self->ivShow('editHash', 'primaryDirHash');
                    %dirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'primaryAbbrevHash');
                    %abbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'primaryOppHash');
                    %oppHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'primaryOppAbbrevHash');
                    %oppAbbrevHash = %$hashRef;

                } else {

                    %dirHash = $self->editObj->primaryDirHash;
                    %abbrevHash = $self->editObj->primaryAbbrevHash;
                    %oppHash = $self->editObj->primaryOppHash;
                    %oppAbbrevHash = $self->editObj->primaryOppAbbrevHash;
                }

                if ($self->ivExists('editHash', 'secondaryDirList')) {

                    # (If any secondary dir IVs have been changed, then they are ALL in ->editHash)
                    $hashRef = $self->ivShow('editHash', 'secondaryDirHash');
                    %secDirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAbbrevHash');
                    %secAbbrevHash = %$hashRef;

                } else {

                    %secDirHash = $self->editObj->secondaryDirHash;
                    %secAbbrevHash = $self->editObj->secondaryAbbrevHash;
                }

                %relDirHash = $self->editObj->relativeDirHash;
                %relAbbrevHash = $self->editObj->relativeAbbrevHash;

                # (Compose our own temporary %combDirHash, in the form
                #   $combDirHash{custom_primary_dir} = undef
                #   $combDirHash{recognised_secondary_dir} = undef
                foreach my $value (values %dirHash) {

                    # (User is allowed to enter custom 'north' direction for the standard dir north,
                    #   so we don't add that to %combDirHash)
                    if ($dirHash{$standard} ne $value) {

                        $combDirHash{$value} = undef;   # Hash in form $hash{$key} = $value
                    }
                }

                foreach my $value (values %abbrevHash) {

                    if ($abbrevHash{$standard} ne $value) {

                        $combDirHash{$value} = undef;   # Hash in form $hash{$key} = $value
                    }
                }

                foreach my $key (keys %secDirHash) {

                    $combDirHash{$key} = undef;         # Hash in form $hash{$key} = $key
                }

                foreach my $key (keys %secAbbrevHash) {

                    $combDirHash{$key} = undef;         # Hash in form $hash{$key} = undef
                }

                foreach my $value (values %relDirHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$slot} = $value
                }

                foreach my $value (values %relAbbrevHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$slot} = $value
                }

                # Now we can test whether the directions entered by the user are already in use, or
                #   not
                if (exists $combDirHash{$dir}) {

                    $msg = '\'' . $dir . '\' is not available because it is already in use as a'
                            . ' custom primary or a secondary direction';

                } elsif ($abbrev && exists $combDirHash{$abbrev}) {

                    $msg = '\'' . $abbrev . '\' is not available because it is already in use as a'
                            . ' custom primary or a secondary direction';
                }

                if ($msg) {

                    # Don't make any changes
                    $self->showMsgDialogue(
                        'Use changes',
                        'error',
                        $msg,
                        'ok',
                    );

                } else {

                    # Add (or replace) an entry in the hashes
                    $dirHash{$standard} = $dir;
                    $abbrevHash{$standard} = $abbrev;
                    $oppHash{$standard} = $opp;
                    $oppAbbrevHash{$standard} = $oppAbbrev;

                    # Store the modified list/hashes. GA::Obj::Dict->createCombHashes is not called
                    #   until $self->saveChanges, of course
                    $self->ivAdd('editHash', 'primaryDirHash', \%dirHash);
                    $self->ivAdd('editHash', 'primaryAbbrevHash', \%abbrevHash);
                    $self->ivAdd('editHash', 'primaryOppHash', \%oppHash);
                    $self->ivAdd('editHash', 'primaryOppAbbrevHash', \%oppAbbrevHash);

                    # Refresh the list to show the changes
                    $self->directions1Tab_refreshList(
                        $slWidget,
                        (scalar @columnList / 2),
                        $showAllFlag,
                    );

                    # Empty the widgets below the simple list
                    $entry->set_text('');
                    $entry2->set_text('');
                    $entry3->set_text('');
                    $entry4->set_text('');
                    $entry5->set_text('');

                    # Desensitise the widgets below the simple list
                    $self->desensitiseWidgets($entry2, $entry3, $entry4, $entry5);
                }
            }
        });

        # 'Reset list' button
        my $button3 = $self->addButton($grid,
            'Reset list', 'Reset the list of primary directions', undef,
            10, 12, 7, 8);
        $button3->signal_connect('clicked' => sub {

            # Remove the IVs from $self->editHash, so that the IVs in $self->editObj take over
            $self->ivDelete('editHash', 'primaryDirHash');
            $self->ivDelete('editHash', 'primaryAbbrevHash');
            $self->ivDelete('editHash', 'primaryOppHash');
            $self->ivDelete('editHash', 'primaryOppAbbrevHash');

            # Refresh the list to show the changes
            $self->directions1Tab_refreshList($slWidget, (scalar @columnList / 2), $showAllFlag);

            # Empty the widgets below the simple list
            $entry->set_text('');
            $entry2->set_text('');
            $entry3->set_text('');
            $entry4->set_text('');
            $entry5->set_text('');

            # Desensitise the widgets below the simple list
            $self->desensitiseWidgets($entry2, $entry3, $entry4, $entry5);
        });

        # Now we can defined the checkbutton's ->signal_connect
        $checkButton->signal_connect('toggled' => sub {

            if ($showAllFlag) {
                $showAllFlag = FALSE;
            } else {
                $showAllFlag = TRUE;
            }

            $self->directions1Tab_refreshList($slWidget, (scalar @columnList / 2), $showAllFlag);
        });

        # Widgets below the simple list start desensitised (NB $entry is always desensisited)
        $self->desensitiseWidgets($entry, $entry2, $entry3, $entry4, $entry5);

        # Tab complete
        return 1;
    }

    sub directions1Tab_refreshList {

        # Called by $self->directions1Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns in the list
        #
        # Optional arguments
        #   $showAllFlag    - If set to FALSE (or 'undef'), secondary-intercardinal directions
        #                       ('northnortheast', etc) are not shown. If set to TRUE, all primary
        #                       directions are shown
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $showAllFlag, $check) = @_;

        # Local variables
        my (
            $hashRef,
            @dirList, @dataList,
            %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash,
        );

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

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

        # Import the IVs
        if ($self->ivExists('editHash', 'primaryDirHash')) {

            $hashRef = $self->ivShow('editHash', 'primaryDirHash');
            %dirHash = %$hashRef;

            $hashRef = $self->ivShow('editHash', 'primaryAbbrevHash');
            %abbrevHash = %$hashRef;

            $hashRef = $self->ivShow('editHash', 'primaryOppHash');
            %oppHash = %$hashRef;

            $hashRef = $self->ivShow('editHash', 'primaryOppAbbrevHash');
            %oppAbbrevHash = %$hashRef;

        } else {

            %dirHash = $self->editObj->primaryDirHash;
            %abbrevHash = $self->editObj->primaryAbbrevHash;
            %oppHash = $self->editObj->primaryOppHash;
            %oppAbbrevHash = $self->editObj->primaryOppAbbrevHash;
        }

        @dirList = $axmud::CLIENT->constPrimaryDirList;

        # Compile the simple list data
        foreach my $dir (@dirList) {

            if ($showAllFlag || $axmud::CLIENT->ivExists('constShortPrimaryDirHash', $dir)) {

                push (@dataList,
                    $dir,
                    $dirHash{$dir},
                    $abbrevHash{$dir},
                    $oppHash{$dir},
                    $oppAbbrevHash{$dir},
                );
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub directions2Tab {

        # Directions2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $thisDir, $noAutoString,
            @columnList, @comboList,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Secondary directions'],
        );

        # Primary directions
        $self->addLabel($grid, '<b>Secondary directions</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Customisable list of secondary directions</i>',
            1, 12, 1, 2);

        # Create the simple list
        @columnList = (
            'Custom', 'text',
            'Abbrev\'n', 'text',
            'Opposite dirs', 'text',
            'Opp abbrev\'d dirs', 'text',
            'Auto-allocated to', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->directions2Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Entry/combo boxes
        $self->addLabel($grid, 'Direction',
            1, 2, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            2, 6, 9, 10);

        $self->addLabel($grid, 'Abbreviation',
            6, 8, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            8, 12, 9, 10);

        $self->addLabel($grid, 'Opposite dirs',
            1, 2, 10, 11);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            2, 6, 10, 11);

        $self->addLabel($grid, 'Opposite abbrev dirs',
            6, 8, 10, 11);
        my $entry4 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            8, 12, 10, 11);

        @comboList = qw(
            north northeast east southeast south southwest west northwest up down
            northnortheast eastnortheast eastsoutheast southsoutheast
            southsouthwest westsouthwest westnorthwest northnorthwest
        );

        $noAutoString = 'Don\'t auto-allocate';
        unshift(@comboList, $noAutoString);

        $self->addLabel($grid, 'Auto-allocate to',
            1, 2, 11, 12);
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            2, 6, 11, 12);

        # 'Unselect' button
        my $button = $self->addButton($grid,
            'Unselect', 'Unselect the selected direction', undef,
            1, 2, 8, 9);
        $button->signal_connect('clicked' => sub {

            $slWidget->get_selection->unselect_all();
        });

        # 'Add/Edit direction' button
        my $button2 = $self->addButton($grid,
            'Add/Edit dir', 'Add a new direction or edit the selected one', undef,
            2, 4, 8, 9);
        $button2->signal_connect('clicked' => sub {

            my ($dir, $abbrev, $opp, $oppAbbrev, $auto, $posn);

            ($dir, $abbrev, $opp, $oppAbbrev, $auto)
                = $self->getSimpleListData($slWidget, 0, 1, 2, 3, 4);

            # Sensitise the widgets
            $self->sensitiseWidgets($entry, $entry2, $entry3, $entry4, $combo);

            if (! $dir) {

                # 'Add' direction. Make sure the entry boxes are empty
                $entry->set_text('');
                $entry2->set_text('');
                $entry3->set_text('');
                $entry4->set_text('');
                # Make sure the 'Don't auto-allocate' string is visible at the top of the combo
                $combo->set_active(0);

                # If the user clicks 'Use changes', it's a completely new secondary direction
                $thisDir = undef;

            } else {

                # 'Edit' direction. Fill the entry boxes with the selected direction's data
                $entry->set_text($dir);
                if (defined $abbrev) {

                    $entry2->set_text($abbrev);
                }
                $entry3->set_text($opp);
                $entry4->set_text($oppAbbrev);
                # Make the right choice visible in the combobox
                $posn = 0;  # Default is the 'don't auto-allocate' string
                if ($auto) {

                    OUTER: for (my $index = 0; $index < 18; $index++) {

                        if ($comboList[$index] eq $auto) {

                            $posn = $index;
                            last OUTER;
                        }
                    }
                }

                $combo->set_active($posn);
                $axmud::CLIENT->desktopObj->updateWidgets($self->_objClass . '->directions2Tab');

                # Store the direction that's being edited because the 'Use changes' button needs it
                $thisDir = $dir;
            }
        });

        # 'Delete direction' button
        my $button3 = $self->addButton($grid,
            'Delete dir', 'Delete the selected direction', undef,
            4, 6, 8, 9);
        $button3->signal_connect('clicked' => sub {

            my (
                $dir, $listRef, $hashRef, $count,
                @dirList,
                %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash, %autoHash,
            );

            ($dir) = $self->getSimpleListData($slWidget, 0);
            if ($dir) {

                # Import data from the affected IVs
                if ($self->ivExists('editHash', 'secondaryDirList')) {

                    $listRef = $self->ivShow('editHash', 'secondaryDirList');
                    @dirList = @$listRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryDirHash');
                    %dirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAbbrevHash');
                    %abbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppHash');
                    %oppHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppAbbrevHash');
                    %oppAbbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAutoHash');
                    %autoHash = %$hashRef;

                } else {

                    @dirList = $self->editObj->secondaryDirList;
                    %dirHash = $self->editObj->secondaryDirHash;
                    %abbrevHash = $self->editObj->secondaryAbbrevHash;
                    %oppHash = $self->editObj->secondaryOppHash;
                    %oppAbbrevHash = $self->editObj->secondaryOppAbbrevHash;
                    %autoHash = $self->editObj->secondaryAutoHash;
                }

                # Remove $dir from the list of secondary directions
                $count = -1;
                OUTER: foreach my $otherDir (@dirList) {

                    $count++;

                    if ($otherDir eq $dir) {

                        # Remove the entry
                        splice (@dirList, $count, 1);
                        last OUTER;
                    }
                }

                # Remove $dir from the corresponding hashes
                delete $dirHash{$dir};
                delete $abbrevHash{$dir};
                delete $oppHash{$dir};
                delete $oppAbbrevHash{$dir};
                delete $autoHash{$dir};

                # Store the modified list/hashes
                $self->ivAdd('editHash', 'secondaryDirList', \@dirList);
                $self->ivAdd('editHash', 'secondaryDirHash', \%dirHash);
                $self->ivAdd('editHash', 'secondaryAbbrevHash', \%abbrevHash);
                $self->ivAdd('editHash', 'secondaryOppHash', \%oppHash);
                $self->ivAdd('editHash', 'secondaryOppAbbrevHash', \%oppAbbrevHash);
                $self->ivAdd('editHash', 'secondaryAutoHash', \%autoHash);
                # (If the user was modifying a direction in the entry boxes, those changes are
                #   discarded)
                $thisDir = undef;

                # Refresh the list to show the changes
                $self->directions2Tab_refreshList($slWidget, (scalar @columnList / 2));

                # Empty the widgets below the simple list
                $entry->set_text('');
                $entry2->set_text('');
                $entry3->set_text('');
                $entry4->set_text('');

                # Desensitise the widgets below the simple list
                $self->desensitiseWidgets($entry, $entry2, $entry3, $entry4, $combo);
            }
        });

        # 'Move up' button
        my $button4 = $self->addButton($grid,
            'Move up', 'Move the selected direction up the list', undef,
            6, 8, 8, 9);
        $button4->signal_connect('clicked' => sub {

            my (
                $rowNum, $listRef, $hashRef, $dir,
                @dirList,
                %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash, %autoHash,
            );

            # Get the selected row
            ($rowNum) = $slWidget->get_selected_indices();
            # (Don't move a component already at the top of the list...)
            if (defined $rowNum && $rowNum > 0) {

                # Import data from the affected IVs (if we make a change to one, all of them must
                #   be moved into $self->editHash)
                if ($self->ivExists('editHash', 'secondaryDirList')) {

                    $listRef = $self->ivShow('editHash', 'secondaryDirList');
                    @dirList = @$listRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryDirHash');
                    %dirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAbbrevHash');
                    %abbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppHash');
                    %oppHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppAbbrevHash');
                    %oppAbbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAutoHash');
                    %autoHash = %$hashRef;


                } else {

                    @dirList = $self->editObj->secondaryDirList;
                    %dirHash = $self->editObj->secondaryDirHash;
                    %abbrevHash = $self->editObj->secondaryAbbrevHash;
                    %oppHash = $self->editObj->secondaryOppHash;
                    %oppAbbrevHash = $self->editObj->secondaryOppAbbrevHash;
                    %autoHash = $self->editObj->secondaryAutoHash;
                }

                # Move the item
                $dir = splice (@dirList, $rowNum, 1);
                splice (@dirList, ($rowNum - 1), 0, $dir);

                # Store the modified list/hashes
                $self->ivAdd('editHash', 'secondaryDirList', \@dirList);
                $self->ivAdd('editHash', 'secondaryDirHash', \%dirHash);
                $self->ivAdd('editHash', 'secondaryAbbrevHash', \%abbrevHash);
                $self->ivAdd('editHash', 'secondaryOppHash', \%oppHash);
                $self->ivAdd('editHash', 'secondaryOppAbbrevHash', \%oppAbbrevHash);
                $self->ivAdd('editHash', 'secondaryAutoHash', \%autoHash);

                # Refresh the simple list
                $self->directions2Tab_refreshList($slWidget, scalar (@columnList / 2));

                # The component should still be highlighted, after being moved up
                $slWidget->select($rowNum - 1);
            }
        });

        # 'Move down' button
        my $button5 = $self->addButton($grid,
            'Move down', 'Move the selected direction down the list', undef,
            8, 10, 8, 9);
        $button5->signal_connect('clicked' => sub {

            my (
                $rowNum, $listRef, $hashRef, $dir,
                @dirList,
                %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash, %autoHash,
            );

            # Get the selected row
            ($rowNum) = $slWidget->get_selected_indices();
            if (defined $rowNum) {

                # Import data from the affected IVs (if we make a change to one, all of them must
                #   be moved into $self->editHash)
                if ($self->ivExists('editHash', 'secondaryDirList')) {

                    $listRef = $self->ivShow('editHash', 'secondaryDirList');
                    @dirList = @$listRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryDirHash');
                    %dirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAbbrevHash');
                    %abbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppHash');
                    %oppHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppAbbrevHash');
                    %oppAbbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAutoHash');
                    %autoHash = %$hashRef;

                } else {

                    @dirList = $self->editObj->secondaryDirList;
                    %dirHash = $self->editObj->secondaryDirHash;
                    %abbrevHash = $self->editObj->secondaryAbbrevHash;
                    %oppHash = $self->editObj->secondaryOppHash;
                    %oppAbbrevHash = $self->editObj->secondaryOppAbbrevHash;
                    %autoHash = $self->editObj->secondaryAutoHash;
                }

                # (Don't move a component already at the bottom of the list...)
                if ($rowNum < (scalar @dirList - 1)) {

                    $dir = splice (@dirList, $rowNum, 1);
                    splice (@dirList, ($rowNum + 1), 0, $dir);

                    # Store the modified list/hashes
                    $self->ivAdd('editHash', 'secondaryDirList', \@dirList);
                    $self->ivAdd('editHash', 'secondaryDirHash', \%dirHash);
                    $self->ivAdd('editHash', 'secondaryAbbrevHash', \%abbrevHash);
                    $self->ivAdd('editHash', 'secondaryOppHash', \%oppHash);
                    $self->ivAdd('editHash', 'secondaryOppAbbrevHash', \%oppAbbrevHash);
                    $self->ivAdd('editHash', 'secondaryAutoHash', \%autoHash);

                    # Refresh the simple list
                    $self->directions2Tab_refreshList($slWidget, scalar (@columnList / 2));

                    # The component should still be highlighted, after being moved down
                    $slWidget->select($rowNum + 1);
                }
            }
        });

        # 'Use changes' button
        my $button6 = $self->addButton($grid,
            'Use changes', 'Use the changes to the data', undef,
            8, 12, 11, 12);
        $button6->signal_connect('clicked' => sub {

            my (
                $dir, $abbrev, $opp, $oppAbbrev, $msg, $auto, $listRef, $hashRef, $count,
                @dirList,
                %primDirHash, %primAbbrevHash, %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash,
                %autoHash, %relDirHash, %relAbbrevHash, %combDirHash,
            );

            if ($self->checkEntryIcon($entry, $entry2, $entry3, $entry4)) {

                $dir = $entry->get_text();
                $abbrev = $entry2->get_text();
                $opp = $entry3->get_text();
                $oppAbbrev = $entry4->get_text();

                # $abbrev should be 'undef', rather than an empty string; vice-versa for $oppAbbrev
                if (! $abbrev) {

                    $abbrev = undef;
                }

                if (! $oppAbbrev) {

                    $oppAbbrev = '';
                }

                # (Definitely need to remove any leading/trailing whitespace, as well as replace two
                #   or more whitespace characters in the middle with a single whitespace character
                $dir = $axmud::CLIENT->trimWhitespace($dir, TRUE);
                if ($abbrev) {

                    $abbrev = $axmud::CLIENT->trimWhitespace($abbrev, TRUE);
                }

                $opp = $axmud::CLIENT->trimWhitespace($opp, TRUE);
                if ($oppAbbrev) {

                    $oppAbbrev = $axmud::CLIENT->trimWhitespace($oppAbbrev, TRUE);
                }

                $auto = $combo->get_active_text();
                if ($auto eq $noAutoString) {

                    # User selected 'Don't auto-allocate' in the combobox; the dictionary's
                    #   ->secondaryAutoHash expects a key-value pair, with a value set to 'undef'
                    $auto = undef;
                }

                # Need to check that the user hasn't added a primary direction (standard or custom)
                #   or an existing secondary direction
                # Import data from the affected IVs. We can't use ->combDirHash because it hasn't
                #   been updated yet, if any changes have been made in this window
                if ($self->ivExists('editHash', 'primaryDirHash')) {

                    # (If any primary dir IVs have been changed, then they are ALL in ->editHash)
                    $hashRef = $self->ivShow('editHash', 'primaryDirHash');
                    %primDirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'primaryAbbrevHash');
                    %primAbbrevHash = %$hashRef;

                } else {

                    %primDirHash = $self->editObj->primaryDirHash;
                    %primAbbrevHash = $self->editObj->primaryAbbrevHash;
                }

                %relDirHash = $self->editObj->relativeDirHash;
                %relAbbrevHash = $self->editObj->relativeAbbrevHash;

                if ($self->ivExists('editHash', 'secondaryDirList')) {

                    # (If any secondary dir IVs have been changed, then they are ALL in ->editHash)
                    $listRef = $self->ivShow('editHash', 'secondaryDirList');
                    @dirList = @$listRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryDirHash');
                    %dirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAbbrevHash');
                    %abbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppHash');
                    %oppHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppAbbrevHash');
                    %oppAbbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAutoHash');
                    %autoHash = %$hashRef;

                } else {

                    @dirList = $self->editObj->secondaryDirList;
                    %dirHash = $self->editObj->secondaryDirHash;
                    %abbrevHash = $self->editObj->secondaryAbbrevHash;
                    %oppHash = $self->editObj->secondaryOppHash;
                    %oppAbbrevHash = $self->editObj->secondaryOppAbbrevHash;
                    %autoHash = $self->editObj->secondaryAutoHash;
                }

                # (Compose our own temporary %combDirHash, in the form
                #   $combDirHash{custom_primary_dir} = undef
                #   $combDirHash{recognised_secondary_dir} = undef
                foreach my $value (values %primDirHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$key} = $value
                }

                foreach my $value (values %primAbbrevHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$key} = $value
                }

                foreach my $key (keys %dirHash) {

                    # (User is allowed to enter custom 'in' direction for the existing dir 'in',
                    #   so we don't add that to %combDirHash)
                    if (! $thisDir || $key ne $thisDir) {

                        $combDirHash{$key} = undef;     # Hash in form $hash{$key} = $key
                    }
                }

                foreach my $key (keys %abbrevHash) {

                    if (! $thisDir || $key ne $thisDir) {

                        $combDirHash{$key} = undef;     # Hash in form $hash{$key} = undef
                    }
                }

                foreach my $value (values %relDirHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$slot} = $value
                }

                foreach my $value (values %relAbbrevHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$slot} = $value
                }

                # Now we can test whether the directions entered by the user are already in use, or
                #   not
                if ($axmud::CLIENT->ivExists('constOppDirHash', $dir)) {

                    $msg = '\'' . $dir . '\' is not available because it is a standard primary'
                                . ' direction';

                } elsif ($abbrev && $axmud::CLIENT->ivExists('constOppDirHash', $abbrev)) {

                    $msg = '\'' . $abbrev . '\' is not available because it is a standard primary'
                                . ' direction';

                } elsif (exists $combDirHash{$dir}) {

                    $msg = '\'' . $dir . '\' is not available because it is already in use as a'
                            . ' custom primary or a secondary direction';

                } elsif ($abbrev && exists $combDirHash{$abbrev}) {

                    $msg = '\'' . $abbrev . '\' is not available because it is already in use as a'
                            . ' custom primary or a secondary direction';
                }

                if ($msg) {

                    # Don't make any changes
                    $self->showMsgDialogue(
                        'Use changes',
                        'error',
                        $msg,
                        'ok',
                    );

                } else {

                    if ($thisDir) {

                        # Find the position of the direction being edited, $thisDir, in the list
                        $count = -1;
                        OUTER: foreach my $otherDir (@dirList) {

                            $count++;

                            if ($otherDir eq $thisDir) {

                                # In the ordered list of secondary directions, replace $thisDir with
                                #   $dir (they might be the same, if the user didn't change the text
                                #   in $entry)
                                splice (@dirList, $count, 1, $dir);
                                last OUTER;
                            }
                        }

                    } else {

                        # Add the new direction to the end of the list
                        push (@dirList, $dir);
                    }

                    # Add (or replace) an entry in the hashes
                    $dirHash{$dir} = $dir;
                    $abbrevHash{$dir} = $abbrev;
                    $oppHash{$dir} = $opp;
                    $oppAbbrevHash{$dir} = $oppAbbrev;
                    $autoHash{$dir} = $auto;

                    if (defined $thisDir && $thisDir ne $dir) {

                        # Remove the old entry
                        delete $dirHash{$thisDir};
                        delete $abbrevHash{$thisDir};
                        delete $oppHash{$thisDir};
                        delete $oppAbbrevHash{$thisDir};
                        delete $autoHash{$thisDir};
                    }

                    # Store the modified list/hashes
                    $self->ivAdd('editHash', 'secondaryDirList', \@dirList);
                    $self->ivAdd('editHash', 'secondaryDirHash', \%dirHash);
                    $self->ivAdd('editHash', 'secondaryAbbrevHash', \%abbrevHash);
                    $self->ivAdd('editHash', 'secondaryOppHash', \%oppHash);
                    $self->ivAdd('editHash', 'secondaryOppAbbrevHash', \%oppAbbrevHash);
                    $self->ivAdd('editHash', 'secondaryAutoHash', \%autoHash);
                    # (We are no longer editing $thisDir)
                    $thisDir = undef;

                    # Refresh the list to show the changes
                    $self->directions2Tab_refreshList($slWidget, (scalar @columnList / 2));

                    # Reset the widgets below the simple list
                    $entry->set_text('');
                    $entry2->set_text('');
                    $entry3->set_text('');
                    $entry4->set_text('');
                    $combo->set_active(0);

                    # Desensitise the widgets below the simple list
                    $self->desensitiseWidgets($entry, $entry2, $entry3, $entry4, $combo);
                }
            }
        });

        # 'Reset list' button
        my $button7 = $self->addButton($grid,
            'Reset list', 'Reset the list of secondary directions', undef,
            10, 12, 8, 9);
        $button7->signal_connect('clicked' => sub {

            # Remove the IVs from $self->editHash, so that the IVs in $self->editObj take over
            $self->ivDelete('editHash', 'secondaryDirList');
            $self->ivDelete('editHash', 'secondaryDirHash');
            $self->ivDelete('editHash', 'secondaryAbbrevHash');
            $self->ivDelete('editHash', 'secondaryOppHash');
            $self->ivDelete('editHash', 'secondaryOppAbbrevHash');
            $self->ivDelete('editHash', 'secondaryAutoHash');
            # (We are no longer editing $thisDir)
            $thisDir = undef;

            # Refresh the list to show the changes
            $self->directions2Tab_refreshList($slWidget, (scalar @columnList / 2));

            # Empty the widgets below the simple list
            $entry->set_text('');
            $entry2->set_text('');
            $entry3->set_text('');
            $entry4->set_text('');
            $combo->set_active(0);

            # Desensitise the widgets below the simple list
            $self->desensitiseWidgets($entry, $entry2, $entry3, $entry4, $combo);
        });

        # Widgets below the simple list start desensitised
        $self->desensitiseWidgets($entry, $entry2, $entry3, $entry4, $combo);

        # Tab complete
        return 1;
    }

    sub directions2Tab_refreshList {

        # Called by $self->directions2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $slWidgetRef, $hashRef,
            @dirList, @dataList,
            %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash, %autoHash,
        );

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

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

        # Import the IVs
        if ($self->ivExists('editHash', 'secondaryDirList')) {

            $slWidgetRef = $self->ivShow('editHash', 'secondaryDirList');
            @dirList = @$slWidgetRef;

            $hashRef = $self->ivShow('editHash', 'secondaryDirHash');
            %dirHash = %$hashRef;

            $hashRef = $self->ivShow('editHash', 'secondaryAbbrevHash');
            %abbrevHash = %$hashRef;

            $hashRef = $self->ivShow('editHash', 'secondaryOppHash');
            %oppHash = %$hashRef;

            $hashRef = $self->ivShow('editHash', 'secondaryOppAbbrevHash');
            %oppAbbrevHash = %$hashRef;

            $hashRef = $self->ivShow('editHash', 'secondaryAutoHash');
            %autoHash = %$hashRef;

        } else {

            @dirList = $self->editObj->secondaryDirList;
            %dirHash = $self->editObj->secondaryDirHash;
            %abbrevHash = $self->editObj->secondaryAbbrevHash;
            %oppHash = $self->editObj->secondaryOppHash;
            %oppAbbrevHash = $self->editObj->secondaryOppAbbrevHash;
            %autoHash = $self->editObj->secondaryAutoHash;
        }

        # Compile the simple list data
        foreach my $dir (@dirList) {

            push (@dataList,
                $dirHash{$dir},
                $abbrevHash{$dir},
                $oppHash{$dir},
                $oppAbbrevHash{$dir},
                $autoHash{$dir},
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub directions3Tab {

        # Directions3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @columnList, @list, @comboList,
            %descripHash,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Relative directions'],
        );

        # Relative directions
        $self->addLabel($grid, '<b>Relative directions</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of relative directions (which depend on the direction of movement)</i>',
            1, 12, 1, 2);

        # Create the simple list
        @columnList = (
            'Slot #', 'int',
            'Relative dir', 'text',
            'Abbreviation', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->directions3Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing widgets
        @list = (
            'Slot 0 (equivalent of north)'      => 0,
            'Slot 1 (equivalent of northeast)'  => 1,
            'Slot 2 (equivalent of east)'       => 2,
            'Slot 3 (equivalent of southeast)'  => 3,
            'Slot 4 (equivalent of south)'      => 4,
            'Slot 5 (equivalent of southwest)'  => 5,
            'Slot 6 (equivalent of west)'       => 6,
            'Slot 7 (equivalent of northwest)'  => 7,
        );

        do {

            my ($descrip, $slot);

            $descrip = shift @list;
            $slot = shift @list;

            push (@comboList, $descrip);
            $descripHash{$descrip} = $slot;

        } until (! @list);

        $self->addLabel($grid, 'Slot # (assuming movement from the south)',
            1, 6, 8, 9);
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 12, 8, 9);

        $self->addLabel($grid, 'Direction',
            1, 2, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            2, 6, 9, 10);

        $self->addLabel($grid, 'Abbreviation',
            6, 8, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            8, 12, 9, 10);

        # 'Move down' button
        my $button = $self->addButton($grid,
            'Add', 'Add this relative direction', undef,
            1, 3, 10, 11);
        $button->signal_connect('clicked' => sub {

            my (
                $slot, $dir, $abbrev, $hashRef, $listRef, $msg,
                @dirList,
                %primDirHash, %primAbbrevHash, %dirHash, %abbrevHash, %oppHash, %oppAbbrevHash,
                %relDirHash, %relAbbrevHash, %combDirHash,
            );

            $slot = $descripHash{$combo->get_active_text()};
            $dir = lc($entry->get_text());
            $abbrev = lc($entry2->get_text());

            if ($self->checkEntryIcon($entry, $entry2)) {

                # $abbrev should be 'undef', rather than an empty string
                if (! $abbrev) {

                    $abbrev = undef;
                }

                # (Definitely need to remove any leading/trailing whitespace, as well as replace two
                #   or more whitespace characters in the middle with a single whitespace character
                $dir = $axmud::CLIENT->trimWhitespace($dir, TRUE);
                if ($abbrev) {

                    $abbrev = $axmud::CLIENT->trimWhitespace($abbrev, TRUE);
                }

                # Need to check that the user hasn't added a primary direction (standard or custom)
                #   or an existing secondary direction
                # Import data from the affected IVs. We can't use ->combDirHash because it hasn't
                #   been updated yet, if any changes have been made in this window
                if ($self->ivExists('editHash', 'primaryDirHash')) {

                    # (If any primary dir IVs have been changed, then they are ALL in ->editHash)
                    $hashRef = $self->ivShow('editHash', 'primaryDirHash');
                    %primDirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'primaryAbbrevHash');
                    %primAbbrevHash = %$hashRef;

                } else {

                    %primDirHash = $self->editObj->primaryDirHash;
                    %primAbbrevHash = $self->editObj->primaryAbbrevHash;
                }

                if ($self->ivExists('editHash', 'secondaryDirList')) {

                    # (If any secondary dir IVs have been changed, then they are ALL in ->editHash)
                    $listRef = $self->ivShow('editHash', 'secondaryDirList');
                    @dirList = @$listRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryDirHash');
                    %dirHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryAbbrevHash');
                    %abbrevHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppHash');
                    %oppHash = %$hashRef;
                    $hashRef = $self->ivShow('editHash', 'secondaryOppAbbrevHash');
                    %oppAbbrevHash = %$hashRef;

                } else {

                    @dirList = $self->editObj->secondaryDirList;
                    %dirHash = $self->editObj->secondaryDirHash;
                    %abbrevHash = $self->editObj->secondaryAbbrevHash;
                    %oppHash = $self->editObj->secondaryOppHash;
                    %oppAbbrevHash = $self->editObj->secondaryOppAbbrevHash;
                }

                %relDirHash = $self->editObj->relativeDirHash;
                %relAbbrevHash = $self->editObj->relativeAbbrevHash;

                # (Compose our own temporary %combDirHash, in the form
                #   $combDirHash{custom_primary_dir} = undef
                #   $combDirHash{recognised_secondary_dir} = undef
                foreach my $value (values %primDirHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$key} = $value
                }

                foreach my $value (values %primAbbrevHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$key} = $value
                }

                foreach my $key (keys %dirHash) {

                    $combDirHash{$key} = undef;         # Hash in form $hash{$key} = $key
                }

                foreach my $key (keys %abbrevHash) {

                    $combDirHash{$key} = undef;         # Hash in form $hash{$key} = undef
                }

                foreach my $value (values %relDirHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$slot} = $value
                }

                foreach my $value (values %relAbbrevHash) {

                    $combDirHash{$value} = undef;       # Hash in form $hash{$slot} = $value
                }

                # Now we can test whether the directions entered by the user are already in use, or
                #   not
                if ($axmud::CLIENT->ivExists('constOppDirHash', $dir)) {

                    $msg = '\'' . $dir . '\' is not available because it is a standard primary'
                                . ' direction';

                } elsif ($abbrev && $axmud::CLIENT->ivExists('constOppDirHash', $abbrev)) {

                    $msg = '\'' . $abbrev . '\' is not available because it is a standard primary'
                                . ' direction';

                } elsif (exists $combDirHash{$dir}) {

                    $msg = '\'' . $dir . '\' is not available because it is already in use as a'
                            . ' custom primary or a secondary direction';

                } elsif ($abbrev && exists $combDirHash{$abbrev}) {

                    $msg = '\'' . $abbrev . '\' is not available because it is already in use as a'
                            . ' custom primary or a secondary direction';
                }

                if ($msg) {

                    # Don't make any changes
                    $self->showMsgDialogue(
                        'Add relative direction',
                        'error',
                        $msg,
                        'ok',
                    );

                } else {

                    # Update IVs
                    $self->modifyEditHash_hashIV('relativeDirHash', $slot, $dir);
                    $self->modifyEditHash_hashIV('relativeAbbrevHash', $slot, $abbrev);
                    # Refresh the list to show the changes
                    $self->directions3Tab_refreshList($slWidget, (scalar @columnList / 2));
                }
            }
        });

        # 'Delete direction' button
        my $button2 = $self->addButton($grid,
            'Delete', 'Empty the selected slot', undef,
            3, 5, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my $slot;

            ($slot) = $self->getSimpleListData($slWidget, 0);
            if (defined $slot) {

                # Update IVs. The TRUE arguments means to delete the key-value pair
                $self->modifyEditHash_hashIV('relativeDirHash', $slot, undef, TRUE);
                $self->modifyEditHash_hashIV('relativeAbbrevHash', $slot, undef, TRUE);
                # Refresh the list to show the changes
                $self->directions3Tab_refreshList($slWidget, (scalar @columnList / 2));
            }
        });


        # 'Reset list' button
        my $button3 = $self->addButton($grid,
            'Reset list', 'Reset the list of relative directions', undef,
            10, 12, 10, 11);
        $button3->signal_connect('clicked' => sub {

            # Remove the IVs from $self->editHash, so that the IVs in $self->editObj take over
            $self->ivDelete('editHash', 'relativeDirHash');
            $self->ivDelete('editHash', 'relativeAbbrevHash');
            # Refresh the list to show the changes
            $self->directions3Tab_refreshList($slWidget, (scalar @columnList / 2));

            # Empty the widgets below the simple list
            $combo->set_active(0);
            $entry->set_text('');
            $entry2->set_text('');
        });

        # Tab complete
        return 1;
    }

    sub directions3Tab_refreshList {

        # Called by $self->directions3Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @dataList,
            %dirHash, %abbrevHash,
        );

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

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

        # Import the IVs
        %dirHash = $self->getEditHash_hashIV('relativeDirHash');
        %abbrevHash = $self->getEditHash_hashIV('relativeAbbrevHash');

        # Compile the simple list data
        for (my $index = 0; $index < 8; $index++) {

            push (@dataList,
                $index,
                $dirHash{$index},
                $abbrevHash{$index},
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub directions4Tab {

        # Directions4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Speedwalking characters'],
        );

        # Speedwalking characters
        $self->addLabel($grid, '<b>Speedwalking characters</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of speedwalking characters and the movement commands they represent</i>',
            1, 12, 1, 2);

        # Create the simple list
        @columnList = (
            'Char', 'text',
            'Movement command', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'speedDirHash');

        # Editing widgets
        $self->addLabel($grid, 'Speedwalking character (a-z)',
            1, 4, 10, 11);
        my $entry = $self->addEntryWithIcon(
            $grid,
            undef,
            \&directions4Tab_checkEntry,
            undef,
            undef,
            4, 6, 10, 11,
            1, 1);      # Max width
        $self->addLabel($grid, 'Movement command',
            6, 8, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            8, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'speedDirHash',
            11,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($char, $cmd);

            $char = lc($entry->get_text());
            $cmd = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('speedDirHash', $char, $cmd);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'speedDirHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub directions4Tab_checkEntry {

        # Called by $self->directions4Tab to check the text in the Gtk3::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk3::Entry
        #
        # Return values
        #   'undef' on improper arguments or if $text is invalid
        #   1 if $text is valid

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

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

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

        if ($text =~ m/^[[:alpha:]]$/) {
            return 1;
        } else {
            return undef;
        }
    }

    sub directions5Tab {

        # Directions5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $cageObj, $replaceCmd,
            @columnList, @comboList, @sortedList,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Speedwalking modifier characters'],
        );

        # Get the current highest-priority command cage
        $cageObj = $self->session->findHighestCage('cmd');

        # Speedwalking modifier characters
        $self->addLabel($grid, '<b>Speedwalking modifier characters</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of speedwalking modifier characters and the standard commands they'
            . ' represent</i>',
            1, 12, 1, 2);

        # Create the simple list
        @columnList = (
            'Char', 'text',
            'Standard command', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'speedModifierHash');

        # Editing widgets
        $self->addLabel($grid, 'Modifier character (A-Z)',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon(
            $grid,
            undef,
            \&directions3Tab_checkEntry,
            undef,
            undef,
            4, 6, 8, 9,
            1, 1);      # Max width

        $self->addLabel($grid, 'Standard command',
            6, 8, 8, 9);

        @comboList = (
            $cageObj->moveCmdList,
            'open_dir', 'close_dir', 'unlock', 'unlock_with', 'lock', 'lock_with', 'pick',
            'pick_with', 'break', 'break_with',
        );
        @sortedList = sort {$a cmp $b} (@comboList);
        my $combo = $self->addComboBox($grid, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            8, 12, 8, 9);
        # (->signal_connect appears below)

        $self->addLabel($grid, 'Current replacement command',
            1, 4, 9, 10);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            4, 12, 9, 10);
        $replaceCmd = $self->session->findCmd($combo->get_active_text());
        if ($replaceCmd) {

            $entry2->set_text($replaceCmd);
        }

        # (->signal_connect from above)
        $combo->signal_connect('changed' => sub {

            $replaceCmd = $self->session->findCmd($combo->get_active_text());
            if (! $replaceCmd) {
                $entry2->set_text('');
            } else {
                $entry2->set_text($replaceCmd);
            }
        });

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'speedModifierHash',
            10,
            $entry, $combo,
        );
        $button->signal_connect('clicked' => sub {

            my ($char, $cmd);

            $char = uc($entry->get_text());
            $cmd = $combo->get_active_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('speedModifierHash', $char, $cmd);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'speedModifierHash',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Exit;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Exit')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['General properties'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }


    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->assistedMovesTab();
        $self->doorsTab();
        $self->randomTab();
        $self->ornamentsTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves any changes made to data stored by the edit object
        # (We don't use the inherited function, because we need to tell Automapper windows to update
        #   themselves - even if $self->editHash is empty)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $wmObj, $randomType, $newListRef,
            @oldList, @newList, @modNewList, @removeList, @addList,
            %hash,
        );

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

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

        if ($self->editHash) {

            # Import the world model (for convenience)
            $wmObj = $self->session->worldModelObj;

            # Respond, if the user has been changes to ->randomType or ->randomDestList
            if ($self->ivExists('editHash', 'randomType')) {

                $randomType = $self->ivShow('editHash', 'randomType');
                if ($randomType ne $self->editObj->randomType) {

                    # New value for ->randomType, which requires a call to the world model
                    if ($randomType ne 'none') {

                        $wmObj->restoreRandomExit(
                            TRUE,               # Update Automapper windows
                            $self->editObj,
                        );

                    } else {

                        $wmObj->setRandomExit(
                            TRUE,               # Update Automapper windows
                            $self->editObj,
                            $randomType,
                        );
                    }
                }

                $self->ivDelete('editHash', 'randomType');
            }

            if ($self->ivExists('editHash', 'randomDestList')) {

                # Import the existing and new versions of ->randomDestList
                @oldList = $self->editObj->randomDestList;
                $newListRef = $self->ivShow('editHash', 'randomDestList');
                @newList = @$newListRef;

                # Check all the items in @newList, removing any items which are not valid room
                #   model numbers (and removing duplicates)
                foreach my $roomNum (@newList) {

                    if (
                        $wmObj->ivExists('roomModelHash', $roomNum)
                        && (! exists $hash{$roomNum})
                    ) {
                        push (@modNewList, $roomNum);
                        $hash{$roomNum} = undef;
                    }
                }

                # Compile a list of room numbers that were in the old ->randomDestList, but are not
                #   in the new one, and which therefore need to be removed as random exit
                #   destinations (stored in @removeList)
                OUTER: foreach my $oldRoomNum (@oldList) {

                    INNER: foreach my $newRoomNum (@newList) {

                        if ($oldRoomNum == $newRoomNum) {

                            next OUTER;
                        }
                    }

                    # $oldRoomNum is missing from @newList
                    push (@removeList, $oldRoomNum);
                }

                # Compile a list of room numbers that are in the new ->randomDestList, but were not
                #   in the old one, and which therefore need to be added as random exit
                #   destinations (stored in @addList)
                OUTER: foreach my $newRoomNum (@modNewList) {

                    INNER: foreach my $oldRoomNum (@oldList) {

                        if ($newRoomNum == $oldRoomNum) {

                            next OUTER;
                        }
                    }

                    # $newRoomNum is missing from @oldList
                    push (@addList, $newRoomNum);
                }

                # The rooms in @removeList must be informed that they are no longer destination
                #   rooms for a random exit
                foreach my $roomNum (@removeList) {

                    my $roomObj = $wmObj->ivShow('modelHash', $roomNum);

                    $wmObj->addRandomDestination($roomObj, $self->editObj);
                }

                # The rooms in @addList must be informed that they are now destination rooms for a
                #   random exit
                foreach my $roomNum (@addList) {

                    my $roomObj = $wmObj->ivShow('modelHash', $roomNum);

                    $wmObj->removeRandomDestination($roomObj, $self->editObj);
                }

                # Now set the new value of ->randomDestList. The order matters, so we don't sort
                #   the list
                $self->editObj->ivPoke('randomDestList', @modNewList);

                $self->ivDelete('editHash', 'randomDestList');
            }

#            if ($self->ivExists('editHash', 'altDir')) {
#
#                if ($self->ivShow('editHash', 'altDir') eq '') {
#
#                    # Use 'undef' rather than an empty string
#                    $self->editObj->ivUndef('altDir');
#
#                } else {
#
#                    $self->editObj->ivPoke('altDir', $self->ivShow('editHash', 'altDir'));
#                }
#
#                $self->ivDelete('editHash', 'altDir');
#            }

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        # For objects stored in the exit model (first checking that they are still there)...
        if (
            $self->editObj->modelFlag
            && $self->session->worldModelObj->ivExists('exitModelHash', $self->editObj->number)
            && $self->session->worldModelObj->ivShow('exitModelHash', $self->editObj->number)
                eq $self->editObj
        ) {
            # Mark this room to be redrawn, if it is visible
            $self->session->worldModelObj->updateMaps('exit', $self->editObj);
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>General properties</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Exit model #',
            1, 3, 2, 3);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Parent room #',
            1, 3, 3, 4);
        $self->addEntry($grid, 'parent', FALSE,
            3, 6, 3, 4);

        $self->addLabel($grid, 'Exit type',
            1, 3, 4, 5);
        $self->addEntry($grid, 'exitType', FALSE,
            3, 6, 4, 5);

        $self->addLabel($grid, 'Nominal direction',
            1, 3, 5, 6);
        $self->addEntry($grid, 'dir', FALSE,
            3, 6, 5, 6);
        $self->addLabel($grid, 'Alternative direction(s)',
            1, 3, 6, 7);
        $self->addEntry($grid, 'altDir', FALSE,
            3, 6, 6, 7);
        $self->addLabel($grid, 'Mapped as direction',
            1, 3, 7, 8);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 7, 8);
        if ($self->editObj->mapDir) {
            $entry->set_text($self->editObj->mapDir);
        } else {
            $entry->set_text('unallocatable');
        }
        $self->addLabel($grid, 'Draw mode',
            1, 3, 8, 9);
        $self->addEntry($grid, 'drawMode', FALSE,
            3, 6, 8, 9);
        $self->addLabel($grid, 'Destination room #',
            1, 3, 9, 10);
        $self->addEntry($grid, 'destRoom', FALSE,
            3, 6, 9, 10);
        $self->addLabel($grid, 'Twin exit #',
            1, 3, 10, 11);
        $self->addEntry($grid, 'twinExit', FALSE,
            3, 6, 10, 11);
        $self->addLabel($grid, 'Shadow exit #',
            1, 3, 11, 12);
        $self->addEntry($grid, 'shadowExit', FALSE,
            3, 6, 11, 12);


        # Right column (add empty label for spacing)
        $self->addLabel($grid, '',
            6, 7, 1, 2);
        $self->addCheckButton($grid, 'Broken exit flag', 'brokenFlag', FALSE,
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Bent broken exit flag', 'bentFlag', FALSE,
            7, 12, 2, 3);
        $self->addCheckButton($grid, 'Region exit flag', 'regionFlag', FALSE,
            7, 12, 3, 4);
        $self->addCheckButton($grid, 'Region super-exit flag', 'superFlag', FALSE,
            7, 12, 4, 5);
        $self->addCheckButton($grid, 'One-way exit flag', 'oneWayFlag', FALSE,
            7, 12, 5, 6);
        $self->addLabel($grid, 'Incoming 1-way dir',
            7, 9, 6, 7);
        $self->addEntry($grid, 'oneWayDir', FALSE,
            9, 12, 6, 7);
        $self->addCheckButton($grid, 'Retracing exit flag', 'retraceFlag', FALSE,
            7, 12, 7, 8);
        $self->addCheckButton($grid, 'Hidden exit flag', 'hiddenFlag', FALSE,
            7, 12, 8, 9);
        $self->addLabel($grid, 'Exit tag',
            7, 9, 9, 10);
        $self->addEntry($grid, 'exitTag', FALSE,
            9, 12, 9, 10);
        $self->addLabel($grid, 'Exit tag X offset',
            7, 9, 10, 11);
        $self->addEntry($grid, 'exitTagXOffset', FALSE,
            9, 12, 10, 11);
        $self->addLabel($grid, 'Exit tag Y offset',
            7, 9, 11, 12);
        $self->addEntry($grid, 'exitTagYOffset', FALSE,
            9, 12, 11, 12);

        return 1;
    }

    sub assistedMovesTab {

        # Assisted Moves tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @profList, @sortedList);

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

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

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Assisted moves');

        # Assisted moves
        $self->addLabel($grid, '<b>Assisted moves</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Command sequences used in assisted moves for this exit, e.g.'
            . ' \'push button;north\'</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Profile', 'text',
            'Command sequence', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'assistedHash');

        # Get a sorted list of all non-world profiles...
        foreach my $profObj ($self->session->ivValues('profHash')) {

            if ($profObj->category ne 'world') {

                push (@profList, $profObj->name);
            }
        }

        @sortedList = sort {lc($a) cmp lc($b)} (@profList);
        # ...and put the current world profile at the top of the list
        unshift (@sortedList, $self->session->currentWorld->name);

        # Add entries/comboboxes for adding world model objects
        $self->addLabel($grid, 'Profile',
            1, 3, 8, 9);
        my $combo = $self->addComboBox($grid, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($grid, 'Command sequence',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'assistedHash',
            10,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($profName, $sequence);

            $profName = $combo->get_active_text();
            $sequence = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('assistedHash', $profName, $sequence);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'assistedHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub doorsTab {

        # Doors tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @comboList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Doors',
            ['Door commands'],
        );

        # Door commands
        $self->addLabel($grid, '<b>Door commands</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Command sequences used to get through this exit\'s door (if any)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Door type', 'text',
            'Command sequence', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'doorHash');

        # Add entries/comboboxes for adding world model objects
        $self->addLabel($grid, 'Profile',
            1, 3, 8, 9);
        @comboList = qw(break pick unlock open close lock);
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($grid, 'Command sequence',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'assistedHash',
            10,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($type, $sequence);

            $type = $combo->get_active_text();
            $sequence = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('doorHash', $type, $sequence);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'doorHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub randomTab {

        # Random tab
        #
        # 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 . '->randomTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Random',
            ['Random exit type'],
        );

        # Left column
        $self->addLabel($grid, '<b>Random exit type</b>',
            0, 6, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'none\'', 'randomType',
            'none',         # IV set to this value when toggled
            TRUE,           # Sensitised widget
            1, 3, 1, 2);
        $self->addLabel($grid, 'Not a random exit',
            3, 6, 1, 2);

        # Right column
        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'same_region\'', 'randomType', 'same_region', TRUE,
            7, 9, 1, 2);
        $self->addLabel($grid, 'Leads to random room in same region',
            9, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'any_region\'', 'randomType', 'any_region', TRUE,
            7, 9, 2, 3);
        $self->addLabel($grid, 'Leads to random room anywhere',
            9, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'temp_region\'', 'randomType', 'temp_region', TRUE,
            7, 9, 3, 4);
        $self->addLabel($grid, 'Creates new room in temporary region',
            9, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'room_list\'', 'randomType', 'room_list', TRUE,
            7, 9, 4, 5);
        $self->addLabel($grid, 'Leads to random room from this list:',
            9, 12, 4, 5);
        $self->addLabel($grid, '<i>(Add a list of room numbers, one per line)</i>',
            9, 12, 5, 6);

        $self->addTextView($grid, 'randomDestList', TRUE,
            9, 12, 6, 12);

        # Tab complete
        return 1;
    }

    sub ornamentsTab {

        # Ornaments tab
        #
        # 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 . '->ornamentsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Ornaments',
            ['Exit ornaments', 'Exit information', 'Exit current state'],
        );

        # Left column
        $self->addLabel($grid, '<b>Exit ornaments</b>',
            0, 6, 0, 1);

        my $button = $self->addCheckButton($grid, 'Breakable door', undef, FALSE,
            1, 6, 1, 2);
        my $button2 = $self->addCheckButton($grid, 'Pickable lock', undef, FALSE,
            1, 6, 2, 3);
        my $button3 = $self->addCheckButton($grid, 'Lockable door', undef, FALSE,
            1, 6, 3, 4);
        my $button4 = $self->addCheckButton($grid, 'Openable door', undef, FALSE,
            1, 6, 4, 5);
        my $button5 = $self->addCheckButton($grid, 'Exit impassable', undef, FALSE,
            1, 6, 5, 6);
        my $button6 = $self->addCheckButton($grid, 'Mystery exit', undef, FALSE,
            1, 6, 6, 7);

        if ($self->editObj->exitOrnament eq 'break') {
            $button->set_active(TRUE);
        } elsif ($self->editObj->exitOrnament eq 'pick') {
            $button2->set_active(TRUE);
        } elsif ($self->editObj->exitOrnament eq 'lock') {
            $button3->set_active(TRUE);
        } elsif ($self->editObj->exitOrnament eq 'open') {
            $button4->set_active(TRUE);
        } elsif ($self->editObj->exitOrnament eq 'impass') {
            $button5->set_active(TRUE);
        } elsif ($self->editObj->exitOrnament eq 'mystery') {
            $button6->set_active(TRUE);
        }

        # Exit information
        $self->addLabel(
            $grid,
            '<b>Exit information</b>',
            0, 6, 7, 8);
        $self->addLabel(
            $grid,
            '<i>(e.g. further details about the destination room)</i>',
            1, 6, 8, 9);
        $self->addEntryWithButton($grid, 'exitInfo', TRUE,
            1, 6, 9, 10);

        # Right column
        $self->addLabel($grid, '<b>Exit current state</b>',
            6, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'normal\'', 'exitState',
            'normal',              # IV set to this value when toggled
            FALSE,          # Insensitive widget
            7, 9, 1, 2);
        $self->addLabel($grid, 'Exit passable/state unknown',
            9, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'open\'', 'exitState', 'open', FALSE,
            7, 9, 2, 3);
        $self->addLabel($grid, 'Exit open',
            9, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'closed\'', 'exitState', 'closed', FALSE,
            7, 9, 3, 4);
        $self->addLabel($grid, 'Exit closed',
            9, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'locked\'', 'exitState', 'locked', FALSE,
            7, 9, 4, 5);
        $self->addLabel($grid, 'Exit locked',
            9, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'secret\'', 'exitState', 'secret', FALSE,
            7, 9, 5, 6);
        $self->addLabel($grid, 'Secret exit',
            9, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'secret_open\'', 'exitState', 'secret_open', FALSE,
            7, 9, 6, 7);
        $self->addLabel($grid, 'Secret open exit',
            9, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'secret_closed\'', 'exitState', 'secret_closed', FALSE,
            7, 9, 7, 8);
        $self->addLabel($grid, 'Secret closed exit',
            9, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'secret_locked\'', 'exitState', 'secret_locked', FALSE,
            7, 9, 8, 9);
        $self->addLabel($grid, 'Secret locked exit',
            9, 12, 8, 9);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'impass\'', 'exitState', 'impass', FALSE,
            7, 9, 9, 10);
        $self->addLabel($grid, 'Exit impassable',
            9, 12, 9, 10);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'dark\'', 'exitState', 'dark', FALSE,
            7, 9, 10, 11);
        $self->addLabel($grid, 'Destination room is dark',
            9, 12, 10, 11);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'danger\'', 'exitState', 'danger', FALSE,
            7, 9, 11, 12);
        $self->addLabel($grid, 'Destination room is dangerous',
            9, 12, 11, 12);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'emphasis\'', 'exitState', 'emphasis', FALSE,
            7, 9, 12, 13);
        $self->addLabel($grid, 'Exit emphasised',
            9, 12, 12, 13);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'other\'', 'exitState', 'other', FALSE,
            7, 9, 13, 14);
        $self->addLabel($grid, 'Other state',
            9, 12, 13, 14);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Generic::Cage;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

#   sub checkEditObj {}         # Inherited from GA::Generic::ConfigWin

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Cage settings'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    sub compileCages {

        # Compiles a list of cages, starting with this one, continuing with its inferior cage, and
        #   with that cage's inferior cage (and so on)
        # Returns the list, with this cage in first position, and the least superior cage in last
        #   position
        # If this cage has no inferior, the list will contain only this cage
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, the list described above

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

        # Local variables
        my (
            $lowerCage, $lowestCage,
            @cageList,
        );

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

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

        # Check this cage's inferior, and that cage's inferior, and so on until the least superior
        #   cage is found
        $lowerCage = $self->editObj;

        do {

            my $thisCage = $self->session->ivShow('inferiorCageHash', $lowerCage->name);

            if (defined $thisCage) {

                push (@cageList, $lowerCage);
                $lowerCage = $thisCage;

            } else {

                push (@cageList, $lowerCage);
                $lowestCage = $lowerCage;
            }

        } until ($lowestCage);

        return @cageList;
    }

    # Notebook tabs used by more than one cage 'edit' window

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $obj;

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>Cage settings</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, '<i>Cage</i>',
            1, 12, 1, 2);
        $self->addLabel($grid, 'Cage name',
            2, 5, 2, 3);
        $self->addEntry($grid, 'name', FALSE,
            5, 12, 2, 3, 42, 42);               # Cage's max names are 42 characters

        $self->addLabel($grid, 'Cage type',
            2, 5, 3, 4);
        $self->addEntry($grid, 'cageType', FALSE,
            5, 7, 3, 4, 8, 8);

        $self->addLabel($grid, 'Inferior cage',
            2, 5, 4, 5);
        my $entry = $self->addEntry($grid, undef, 0,
            5, 12, 4, 5);
        if ($self->session->ivExists('inferiorCageHash', $self->editObj->name)) {

            $obj = $self->session->ivShow('inferiorCageHash', $self->editObj->name);
            $entry->set_text($obj->name);
        }

        $self->addLabel($grid, '<i>Associated profile</i>',
            1, 12, 5, 6);

        $self->addLabel($grid, 'Profile name',
            2, 5, 6, 7);
        $self->addEntry($grid, 'profName', FALSE,
            5, 7, 6, 7, 16, 16);

        # Right column
        $self->addCheckButton($grid, 'Standard cage', 'standardFlag', FALSE,
            8, 12, 3, 4);

        $self->addLabel($grid, 'Profile category',
            8, 10, 6, 7);
        $self->addEntry($grid, 'profCategory', FALSE,
            10, 12, 6, 7);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Generic::CageMask;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Cage Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

#   sub checkEditObj {}         # Inherited from GA::Generic::ConfigWin

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which type of cage is being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->maskTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs used by more than one cage 'edit' window

    sub maskTab {

        # Mask tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@cageList, @columnList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Mask',
            ['Cage mask'],
        );

        # Cage mask
        $self->addLabel($grid, '<b>Cage mask</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of properties (IVs) used by this cage mask</i>',
            1, 12, 1, 2);

        # Add a simple list. The number of columns is equal to the number of cages between this one
        #   and the least superior cage. (If this cage has no inferior, there's only one column)
        # Each column (except the first two) has a title containing the cage's associated profile
        @cageList = $self->compileCages();

        # Use different text in the column for the first item in @cageList
        @columnList = (
            'Property (IV)', 'text',
            'Value/Size', 'text',
        );

        shift @cageList;

        foreach my $cage (@cageList) {

            push (@columnList,
                '[' . $cage->profCategory . ': ' . $cage->profName . ']',
                'text',
            );
        }

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->maskTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add some buttons and entry boxes

        $self->addLabel($grid, 'Selected property:',
            1, 3, 10, 11);

        # The IV's corresponding value is either 'undef', or a scalar/list/hash reference (not a
        #   plain scalar)
        my $button = $self->addButton($grid, 'Edit as scalar', 'Edit value as a scalar', undef,
            3, 5, 10, 11);
        $button->signal_connect('clicked' => sub {

            my (
                $prop, $value, $refType, $refName,
                %maskHash,
            );

            ($prop) = $self->getSimpleListData($slWidget, 0);
            if (defined $prop) {

                # Check the corresponding value is either 'undef' or a scalar reference
                %maskHash = $self->getEditHash_hashIV('maskHash');
                if (defined $maskHash{$prop}) {

                    $refType = ref $maskHash{$prop};
                    if ($refType eq 'ARRAY') {
                        $refName = 'a list';
                    } elsif ($refType eq 'HASH') {
                        $refName = 'a hash';
                    } else {
                        $refName = 'a scalar';
                    }
                }

                if (defined $refType && ($refType eq 'ARRAY' || $refType eq 'HASH')) {

                    $self->showMsgDialogue(
                        'Edit scalar',
                        'error',
                        'The \'' . $prop . '\' property is currently ' . $refName
                        . ', not a scalar',
                        'ok',
                    );

                } else {

                    $self->promptScalar(
                        'maskHash',
                        $prop,
                        TRUE,                   # This is a cage mask!
                        $slWidget,
                        FALSE,                  # Not read-only
                        'maskTab_refreshList',
                        $slWidget,              # Pass these arguments to ->maskTab_refreshList
                        (scalar @columnList / 2),
                    );
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Edit as list', 'Edit value as a list', undef,
            5, 7, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my (
                $prop, $value, $refType, $refName,
                %maskHash,
            );

            ($prop) = $self->getSimpleListData($slWidget, 0);
            if (defined $prop) {

                # Check the corresponding value is either 'undef' or a list reference
                %maskHash = $self->getEditHash_hashIV('maskHash');
                if (defined $maskHash{$prop}) {

                    $refType = ref $maskHash{$prop};
                    if ($refType eq 'ARRAY') {
                        $refName = 'a list';
                    } elsif ($refType eq 'HASH') {
                        $refName = 'a hash';
                    } else {
                        $refName = 'a scalar';
                    }
                }

                if (defined $refType && $refType ne 'ARRAY') {

                    $self->showMsgDialogue(
                        'Edit list',
                        'error',
                        'The \'' . $prop . '\' property is currently ' . $refName
                        . ', not a list',
                        'ok',
                    );

                } else {

                    $self->promptList(
                        'maskHash',
                        $prop,
                        $slWidget,
                        FALSE,                  # Not read-only
                        'maskTab_refreshList',
                        $slWidget,              # Pass these arguments to ->maskTab_refreshList
                        (scalar @columnList / 2),
                    );
                }
            }
        });

        my $button3 = $self->addButton($grid, 'Edit as hash', 'Edit value as a hash', undef,
            7, 9, 10, 11);
        $button3->signal_connect('clicked' => sub {

            my (
                $prop, $value, $refType, $refName,
                %maskHash,
            );

            ($prop) = $self->getSimpleListData($slWidget, 0);
            if (defined $prop) {

                # Check the corresponding value is either 'undef' or a hash reference
                %maskHash = $self->getEditHash_hashIV('maskHash');
                if (defined $maskHash{$prop}) {

                    $refType = ref $maskHash{$prop};
                    if ($refType eq 'ARRAY') {
                        $refName = 'a list';
                    } elsif ($refType eq 'HASH') {
                        $refName = 'a hash';
                    } else {
                        $refName = 'a scalar';
                    }
                }

                if (defined $refType && $refType ne 'HASH') {

                    $self->showMsgDialogue(
                        'Edit list',
                        'error',
                        'The \'' . $prop . '\' property is currently ' . $refName
                        . ', not a hash',
                        'ok',
                    );

                } else {

                    $self->promptHash(
                        'maskHash',
                        $prop,
                        $slWidget,
                        FALSE,                  # Not read-only
                        'maskTab_refreshList',
                        $slWidget,              # Pass these arguments to ->maskTab_refreshList
                        (scalar @columnList / 2),
                    );
                }
            }
        });

        my $button4 = $self->addButton($grid, 'Reset', 'Reset (don\'t use) this property', undef,
            10, 12, 10, 11);
        $button4->signal_connect('clicked' => sub {

            my (
                $prop,
                %maskHash,
            );

            ($prop) = $self->getSimpleListData($slWidget, 0);
            if (defined $prop) {

                %maskHash = $self->getEditHash_hashIV('maskHash');
                if (defined $maskHash{$prop}) {

                    $maskHash{$prop} = undef;
                    $self->ivAdd('editHash', 'maskHash', \%maskHash);

                    $self->maskTab_refreshList($slWidget, scalar (@columnList / 2));
                }
            }
        });

        # Tab complete
        return 1;
    }

    sub maskTab_refreshList {

        # Called by $self->maskTab to reset the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @standardList, @cageList, @dataList,
            %ivHash,
        );

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

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

        # Get a list of IVs from the cage, in a standard order
        @standardList = $self->editObj->maskList;
        # Get a list of columns in the simple list, which depends on how many cages there are
        @cageList = $self->compileCages();
        # (We already know the first cage)
        shift @cageList;

        # Import the mask hash
        %ivHash = $self->getEditHash_hashIV('maskHash');

        # Compile the simple list data
        OUTER: foreach my $iv (@standardList) {

            my ($value, $ref);

            $value = $ivHash{$iv};
            if (! defined $value) {

                push (@dataList, $iv, undef);

            } else {

                $ref = ref $value;
                if ($ref eq 'HASH') {
                    push (@dataList, $iv, scalar (keys %$value));
                } elsif ($ref eq 'ARRAY') {
                    push (@dataList, $iv, scalar @$value);
                } else {
                    push (@dataList, $iv, $$value);
                }
            }

            # Remaining columns
            INNER: foreach my $cage (@cageList) {

                my ($thisValue, $thisRef);

                # The lack of a $self->session argument tells ->ivShow to give us the value stored
                #   in the cage, and not to consult its inferiors (if no value is stored there)
                $thisValue = $cage->ivShow('maskHash', $iv);
                if (! defined $thisValue) {

                    # $cage doesn't store an interpolated form of the command
                    push (@dataList, undef);

                } else {

                    $thisRef = ref $thisValue;
                    if ($thisRef eq 'HASH') {
                        push (@dataList, scalar (keys %$thisValue));
                    } elsif ($thisRef eq 'ARRAY') {
                        push (@dataList, scalar @$thisValue);
                    } else {
                        push (@dataList, $$thisValue);
                    }
                }
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Generic::Interface;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

#   sub checkEditObj {}         # Inherited from GA::Generic::ConfigWin

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Standard interface attributes'],
        );

        # Unusual step - create a hash IV for all 'edit' windows inheriting this one. Each key-value
        #   pair corresponds to a key-value pair in GA::Interface::Trigger->attribHash (etc)
        $self->{attribHash} = {};
        # Also create two list IVs which store the list of keys which will be saved in
        #   GA::Interface::Trigger->beforeHash or ->afterHash
        $self->{beforeList} = [];
        $self->{afterList} = [];
        # $self-saveChanges needs to know when those list IVs have been modified, so we need two
        #   flags, as well. (If the lists are emptied, that counts as a change, so we can't simply
        #   test whether ->beforeList and ->afterList are not empty)
        $self->{beforeFlag} = FALSE;
        $self->{afterFlag} = FALSE;

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves any changes made to data stored by the edit object (which can be three of the four
        #   standard attributes, 'stimulus', 'response' and 'enabled'). Also saves any modified
        #   attributes stored in $self->attribHash
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $stimulus, $interfaceModelObj,
            %attribHash, %beforeHash, %afterHash,
        );

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

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

        if ($self->editHash || $self->attribHash || $self->beforeFlag || $self->afterFlag) {

            # Special arrangements for hooks, for which custom hook events must begin with an
            #   underlin
            # If the value of the 'stimulus' attribute does not start with an underline (and is not
            #   a standard hook event), then add one
            if ($self->editObj->category eq 'hook' && $self->ivExists('editHash', 'stimulus')) {

                $stimulus = $self->ivShow('editHash', 'stimulus');
                $interfaceModelObj = $axmud::CLIENT->ivShow(
                    'interfaceModelHash',
                    $self->editObj->category,
                );

                if (
                    defined $stimulus
                    && ! $interfaceModelObj->ivExists('hookEventHash', $stimulus)
                    && ! ($stimulus =~ m/^\_/)
                ) {
                    $self->ivAdd('editHash', 'stimulus', '_' . $stimulus);
                }
            }

            # Transfer the modified non-standard attributes $self->attribHash to the local variable
            #   %attribHash. At the same time, deal with any 'dummy' attributes stored in
            #   $self->attribHash
            foreach my $attrib ($self->ivKeys('attribHash')) {

                my $value = $self->ivShow('attribHash', $attrib);

                # Attributes beginning with an underline are 'dummy' attributes, used to help set
                #   the values of other attributes (e.g. '_substr_num', used to set 'style_mode')
                if (substr($attrib, 0, 1) ne '_') {

                    if ($attrib eq 'style_mode' && $value == -3) {

                        # The attribute's true value is stored in the dummy attribute, '_substr_num'
                        $attribHash{$attrib} = $self->ivShow('attribHash', '_substr_num');

                    } else {

                        # Normal attribute
                        $attribHash{$attrib} = $value;
                    }
                }
            }

            # Add any modified standard attributes to %attribHash. $self->editHash can contain the
            #   keys 'stimulus', 'response' and 'enabled'
            foreach my $attrib ($self->ivKeys('editHash')) {

                if ($attrib eq 'stimulus' || $attrib eq 'response') {
                    $attribHash{$attrib} = $self->ivShow('editHash', $attrib);
                } elsif ($attrib eq 'enabledFlag') {
                    $attribHash{'enabled'} = $self->ivShow('editHash', 'enabledFlag');
                }
            }

            if (%attribHash) {

                # Store the modified standard and non-standard attributes
                $self->editObj->modifyAttribs($self->session, %attribHash);

                # Update every active interface based on the inactive interface, $self->editObj. Do
                #   the update in every affected session, except this one
                foreach my $otherSession ($axmud::CLIENT->listSessions()) {

                    if (
                        $otherSession->currentWorld eq $self->session->currentWorld
                        && $otherSession ne $self->session
                    ) {
                        $otherSession->updateInterfaces($self->editObj, %attribHash);
                    }
                }

                # Now do the update for active interfaces in this session
                $self->session->updateInterfaces($self->editObj, %attribHash);
            }

            # If $self->beforeList or $self->afterList have been modified, store those changes, too
            if ($self->beforeFlag) {

                foreach my $item ($self->beforeList) {

                    $beforeHash{$item} = undef;
                }

                $self->editObj->ivPoke('beforeHash', %beforeHash);
            }

            if ($self->afterFlag) {

                foreach my $item ($self->afterList) {

                    $afterHash{$item} = undef;
                }

                $self->editObj->ivPoke('afterHash', %afterHash);
            }

            # The local IVs can now be reset
            $self->ivEmpty('editHash');
            $self->ivEmpty('attribHash');
            $self->ivEmpty('beforeList');
            $self->ivEmpty('afterList');
            $self->ivPoke('beforeFlag', FALSE);
            $self->ivPoke('afterFlag', FALSE);

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs used by more than one interface 'edit' window

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -   The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $interfaceModelObj, $title,
            @comboList,
        );

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

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

        # Tab setup (already created by the calling function)

        # Get the corresponding interface model object
        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', $self->editObj->category);

        # Standard interface attributes
        $self->addLabel($grid, '<b>Standard interface attributes</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);
        $self->addCheckButton($grid, 'Enabled', 'enabledFlag', TRUE,
            7, 12, 1, 2);

        $self->addLabel($grid, 'Category',
            1, 3, 2, 3);
        $self->addEntry($grid, 'category', FALSE,
            3, 6, 2, 3);

        $self->addLabel($grid, 'Stimulus <i>(' . $interfaceModelObj->stimulusName . ')</i>',
            1, 3, 3, 4);

        if ($self->editObj->category eq 'trigger' || $self->editObj->category eq 'alias') {

            $self->addEntryWithIcon($grid, 'stimulus', 'regex', 1, undef,
                3, 12, 3, 4);

        } elsif ($self->editObj->category eq 'macro') {

            $self->addEntryWithIcon($grid, 'stimulus', 'string', 1, undef,
                3, 12, 3, 4);

        } elsif ($self->editObj->category eq 'timer') {

            $self->addEntryWithIcon($grid, 'stimulus', \&nameTab_checkInterval, undef, undef,
                3, 12, 3, 4);

        } elsif ($self->editObj->category eq 'hook') {

            my $entry = $self->addEntryWithIcon(
                $grid, 'stimulus', \&nameTab_checkHookEvent, undef, undef,
                3, 6, 3, 4);

            # Hooks get an extra combo, for inserting a standard hook event
            @comboList = sort {$a cmp $b} ($interfaceModelObj->ivKeys('hookEventHash'));
            $title = 'Or use standard hook event:';

            my $combo = $self->addComboBox($grid, undef, \@comboList, $title,
                TRUE,               # No 'undef' value used
                6, 12, 3, 4);
            $combo->signal_connect('changed' => sub {

                my $text = $combo->get_active_text();
                if ($text ne $title) {

                    $entry->set_text($text);
                    # Reset the combo
                    $combo->set_active(0);
                }
            });
        }

        # For triggers, the response can be any of 'instruction', 'pattern', 'substitution'
        $self->addLabel($grid, 'Response <i>(' . $interfaceModelObj->responseName . ')</i>',
            1, 3, 4, 5);
        $self->addEntryWithIcon($grid, 'response', 'string', 1, undef,
            3, 12, 4, 5);
        if ($interfaceModelObj->category eq 'trigger') {

            $self->addLabel(
                $grid,
                '<i>NB Splitter triggers: the response is a pattern.</i>',
                3, 12, 5, 6);
            $self->addLabel(
                $grid,
                '<i>NB Rewriter triggers: the response is a substitution</i>',
                3, 12, 6, 7);
        }

        # Tab complete
        return 1;
    }

    sub nameTab_checkInterval {

        # Called by $self->nameTab to check a timer interval is valid
        #
        # Expected arguments
        #   $value      - The value to check, should be a number (minimum value 0.01), or a 24-hour
        #                   clock time in the form HH::MM
        #
        # Return values
        #   'undef' on improper arguments or if $value is invalid
        #   1 if $value is valid

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

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

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

        if (
            ! $axmud::CLIENT->floatCheck($value, 0.1)
            && ! ($value =~ m/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/)
            && ! ($value =~ m/^99\:[0-5][0-9]$/)
        ) {
            return undef;
        } else {
            return 1;
        }
    }

    sub nameTab_checkHookEvent {

        # Called by $self->nameTab to check a hook event is valid
        #
        # Expected arguments
        #   $value  - The value to check, should be a standard hook event, or a custom event whose
        #               name starts with an underline, followed by an alphanumeric character
        #
        # Return values
        #   'undef' on improper arguments or for an invalid hook event
        #   1 for a valid hook event

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

        # Local variables
        my $interfaceModelObj;

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

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

        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', 'hook');

        # Custom hook events must begin with an underline, followed by an alphanumeric character
        # If the user doesn't type an underline, the 'Add' button will add one
        if (
            ! $interfaceModelObj->ivExists('hookEventHash', $value)
            && ! ($value =~ m/^\_[A-Za-z0-9]/)
            && ! ($value =~ m/^[A-Za-z0-9]/)
        ) {
            return undef;
        } else {
            return 1;
        }
    }

    sub beforeAfterTab {

        # BeforeAfter tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my ($single, $plural);

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

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

        $single = $self->editObj->category;
        if ($single eq 'alias') {
            $plural = 'aliases';
        } else {
            $plural = $single . 's';
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Before / after',
            ['Before ' . $plural, 'After ' . $plural],
        );

        # Before
        $self->addLabel($grid, '<b>Before ' . $plural . '</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $grid,
            '<i>When this ' . $single . ' becomes active, it is tested before the following '
            . $plural . '</i>',
            1, 12, 2, 4);
        $self->beforeAfterTab_addTextView($grid, 'before', 4);

        # After
        $self->addLabel($grid, '<b>After ' . $plural . '</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $grid,
            '<i>When this ' . $single . ' becomes active, it is tested after the following '
            . $plural . '</i>',
            1, 12, 8, 10);
        $self->beforeAfterTab_addTextView($grid, 'after', 10);

        # (Explanatory message for triggers only)
        if ($single eq 'trigger') {

            $self->addLabel(
                $grid,
                '<i>(NB All splitter triggers are tested before any other kind of trigger is'
                . ' tested)</i>',
                1, 12, 12, 14);
        }

        # Tab complete
        return 1;
    }

    sub beforeAfterTab_addTextView {

        # Called by $self->beforeAfterTab to create a Gtk3::TextView whose contents are treated as
        #   lists, and saved in $self->beforeList or $self->afterList
        # (We don't use the standard GA::Generic::EditWin->addTextView because we need these
        #   textviews to create lists, which are saved as hashes in $self->editObj)
        #
        # Expected arguments
        #   $grid       - The Gtk3::Grid for this tab
        #   $type       - 'before' or 'after'
        #   $row        - The Gtk3::Grid row where the textview is drawn
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $type, $row, $check) = @_;

        # Local variables
        my @sortedList;

        # Check for improper arguments
        if (! defined $grid || ! defined $type || ! defined $row || defined $check) {

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

        # Create the textview
        my $textView = $self->addTextView($grid, undef, TRUE,
            1, 12, $row, ($row + 2),
            TRUE, TRUE, TRUE, FALSE,    # Treat as list, remove empty lines, do remove whitespace
        );
        my $buffer = $textView->get_buffer();

        # Display a list of the keys in $self->editObj->beforeHash / ->afterHash (sort them
        #   alphabetically, for good measure)
        @sortedList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys($type . 'Hash'));
        $buffer->set_text(join("\n", @sortedList));

        # Store any changes the user makes in $self->$localIV
        $buffer->signal_connect('changed' => sub {

            my (
                $text,
                @list, @finalList,
            );

            $text = $axmud::CLIENT->desktopObj->bufferGetText($buffer);

            # Split the contents of the textview into a list of lines, separated by newline
            #   characters
            @list = split("\n", $text);
            # Remove any empty lines and leading/trailing whitespace, if allowed
            foreach my $line (@list) {

                $line =~ s/^\s+//;  # Remove leading whitespace
                $line =~ s/\s+$//;  # Remove trailing whitespace

                if ($line) {

                    push (@finalList, $line);
                }
            }

            # Store the contents, temporarily, so that they're available to $self->saveChanges
            $self->ivPoke($type . 'List', @finalList);
            # Set a flag, to mark that changes have been made
            $self->ivPoke($type . 'Flag', TRUE);
        });

        return $textView;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    sub attribHash
        { my $self = shift; return %{$self->{attribHash}}; }
    sub beforeList
        { my $self = shift; return @{$self->{beforeList}}; }
    sub afterList
        { my $self = shift; return @{$self->{afterList}}; }
    sub beforeFlag
        { $_[0]->{beforeFlag} }
    sub afterFlag
        { $_[0]->{afterFlag} }
}

{ package Games::Axmud::EditWin::Generic::InterfaceCage;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Cage Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

#   sub checkEditObj {}         # Inherited from GA::Generic::ConfigWin

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Cage settings'],
        );

        # Interface cage 'edit' windows have some non-standard IVs, used to allow one tab to affect
        #   the contents of others and to allow the same code to be inherited efficiently by
        #   different types of interface cage 'edit' window
        # The values for each IV are set in the inheriting object's ->interfacesTab function
        $self->{slWidget1} = undef;
        $self->{slWidget2} = undef;
        $self->{singular} = undef;
        $self->{plural} = undef;
        $self->{interfaceModelObj} = undef;
        $self->{stimulus} = undef;
        $self->{response} = undef;

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which type of cage is being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->interfacesTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs used by more than one cage 'edit' window

    sub interfaces1Tab {

        # Interfaces1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [ucfirst($self->singular) . ' list with attributes'],
        );

        # Interface list
        $self->addLabel($grid, '<b>' . ucfirst($self->singular) . ' list</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of ' . $self->plural . ' belonging to this cage (showing '
            . $self->{singular} . ' attributes)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            ucfirst($self->singular) . ' name', 'text',
            'Enabled', 'bool',
            'Stimulus', 'text',
            'Response', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Unusual step - save the list reference in an IV
        $self->ivPoke('slWidget2', $slWidget);

        # Initialise the list
        $self->interfaces1Tab_refreshList();

        # Add some buttons and entry boxes
        $self->interfacesTab_addButtons($grid, $slWidget, (scalar @columnList / 2));

        # Tab complete
        return 1;
    }

    sub interfaces1Tab_refreshList {

        # Called by $self->interfaces1Tab (etc) to reset the GA::Obj::SimpleList
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@sortedList, @dataList);

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

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

        # Get a sorted list of interface objects from the cage
        @sortedList
            = sort {lc($a->name) cmp lc($b->name)} ($self->editObj->ivValues('interfaceHash'));

        # Compile the simple list data
        foreach my $interfaceObj (@sortedList) {

            push (@dataList,
                $interfaceObj->name,
                $interfaceObj->enabledFlag,
                $interfaceObj->stimulus,
                $interfaceObj->response,
            );
        }

        # Reset the simple list (page 2 has four columns)
        $self->resetListData($self->slWidget2, [@dataList], 4);

        return 1;
    }

    sub interfaces2Tab {

        # Interfaces2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @cageList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            [ucfirst($self->singular) . ' list with lower-priority cages'],
        );

        # Interface list
        $self->addLabel($grid, '<b>' . ucfirst($self->singular) . ' list</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of ' . $self->plural . ' belonging to this cage (compared with lower-priority'
            . ' cages)</i>',
            1, 12, 1, 2);

        # Add a simple list. The number of columns is equal to the number of cages between this one
        #   and the least superior cage. (If this cage has no inferior, there's only one column)
        # Each column (except the first one) has a title containing the cage's associated profile
        @cageList = $self->compileCages();

        # Use different text in the column for the first item in @cageList
        @columnList = (ucfirst($self->singular) . ' name', 'text');
        shift @cageList;

        foreach my $cage (@cageList) {

            push (@columnList,
                '[' . $cage->profCategory . ': ' . $cage->profName . ']',
                'text',
            );
        }

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Unusual step - save the list reference in an IV
        $self->ivPoke('slWidget1', $slWidget);

        # Initialise the list
        $self->interfaces2Tab_refreshList();

        # Add some buttons and entry boxes
        $self->interfacesTab_addButtons($grid, $slWidget, (scalar @columnList / 2));

        # Tab complete
        return 1;
    }

    sub interfaces2Tab_refreshList {

        # Called by $self->interfaces2Tab (etc) to reset the GA::Obj::SimpleList
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@sortedList, @cageList, @dataList);

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

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

        # Get a sorted list of interface names from the cage
        @sortedList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('interfaceHash'));
        # Get a list of columns in the simple list, which depends on how many cages there are
        @cageList = $self->compileCages();

        # Compile the simple list data
        OUTER: foreach my $interfaceName (@sortedList) {

            INNER: foreach my $cage (@cageList) {

                my $interfaceObj;

                if ($cage->ivExists('interfaceHash', $interfaceName)) {

                    # By supplying only two arguments, we instruct ->ivShow to give us the value
                    #   stored in the cage, and not to consult its inferiors (if no value is stored
                    #   there)
                    $interfaceObj = $cage->ivShow('interfaceHash', $interfaceName);
                    push (@dataList, $interfaceObj->name);

                } else {

                    # An interface called $interfaceName isn't stored in $cage
                    push (@dataList, undef);
                }
            }
        }

        # Reset the simple list
        $self->resetListData($self->slWidget1, [@dataList], scalar @cageList);

        return 1;
    }

    sub interfacesTab_addButtons {

        # Called by $self->interfaces1Tab and $self->interfaces2Tab to create the editing buttons
        #   beneath the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $grid       - The current Gtk3::Grid displayed in the notebook
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $interfaceModelObj, $title,
            @comboList,
        );

        # Check for improper arguments
        if (! defined $grid || ! defined $slWidget || ! defined $columns || defined $check) {

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

        # Get the corresponding interface model object
        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', $self->singular);

        # Add entry boxes and edit buttons
        $self->addLabel(
            $grid, ucfirst($self->singular) . ' stimulus <i>(' . $self->stimulus . ')</i>',
            1, 3, 8, 9);
        # For aliases, leave room for an extra button beside the entry
        my ($entry, $combo);
        if ($self->editObj->cageType eq 'trigger') {

            $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
                3, 12, 8, 9);

        } elsif ($self->editObj->cageType eq 'alias') {

            $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
                3, 9, 8, 9);

        } elsif ($self->editObj->cageType eq 'macro') {

            $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
                3, 12, 8, 9);

        } elsif ($self->editObj->cageType eq 'timer') {

            $entry = $self->addEntryWithIcon(
                $grid, undef, \&interfacesTab_checkInterval, undef, undef,
                3, 12, 8, 9);

        } elsif ($self->editObj->cageType eq 'hook') {

            $entry =  $self->addEntryWithIcon(
                $grid, undef, \&interfacesTab_checkHookEvent, undef, undef,
                3, 6, 8, 9);

            # Hooks get an extra combo, for inserting a standard hook event
            @comboList = sort {$a cmp $b} ($interfaceModelObj->ivKeys('hookEventHash'));
            $title = 'Or use standard hook event:';

            $combo = $self->addComboBox($grid, undef, \@comboList, $title,
                TRUE,               # No 'undef' value used
                6, 12, 8, 9);
            $combo->signal_connect('changed' => sub {

                my $text = $combo->get_active_text();
                if ($text ne $title) {

                    $entry->set_text($text);
                    # Reset the combo
                    $combo->set_active(0);
                }
            });
        }

        my $label = $self->addLabel(
            $grid, ucfirst($self->singular) . ' response <i>(' . $self->response . ')</i>',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 9, 10);

        if ($self->singular eq 'trigger') {

            # (Need a bit of extra room for all those checkbuttons)
            $self->addLabel($grid, 'Name <i>(optional)</i>',
                1, 2, 10, 11);

        } else {

            $self->addLabel($grid, 'Name <i>(optional)</i>',
                1, 3, 10, 11);
        }

        my ($entry3, $checkButton, $checkButton2, $checkButton3, $checkButton4);
        if ($self->singular eq 'trigger') {

            $entry3 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
                2, 4, 10, 11);

            $checkButton = $self->addCheckButton($grid, 'Starts enabled', undef, TRUE,
                4, 6, 10, 11);
            $checkButton2 = $self->addCheckButton($grid, 'Splitter', undef, TRUE,
                6, 8, 10, 11);
            $checkButton3 = $self->addCheckButton($grid, 'Rewriter', undef, TRUE,
                8, 10, 10, 11);
            $checkButton4 = $self->addCheckButton($grid, 'Temporary', undef, TRUE,
                10, 12, 10, 11);

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

                if ($checkButton2->get_active()) {
                    $self->ivPoke('response', 'pattern');
                } elsif ($checkButton3->get_active()) {
                    $self->ivPoke('response', 'substitution');
                } else {
                    $self->ivPoke('response', 'instruction');
                }

                $label->set_markup(
                    ucfirst($self->singular) . ' response <i>(' . $self->response . ')</i>',
                );
            });

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

                if ($checkButton2->get_active()) {
                    $self->ivPoke('response', 'pattern');
                } elsif ($checkButton3->get_active()) {
                    $self->ivPoke('response', 'substitution');
                } else {
                    $self->ivPoke('response', 'instruction');
                }

                $label->set_markup(
                    ucfirst($self->singular) . ' response <i>(' . $self->response . ')</i>',
                );
            });

        } else {

            $entry3 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
                3, 6, 10, 11);

            $checkButton = $self->addCheckButton($grid, 'Starts enabled', undef, TRUE,
                6, 9, 10, 11);

            $checkButton4 = $self->addCheckButton($grid, 'Temporary', undef, TRUE,
                9, 12, 10, 11);
        }
        # New interfaces should be enabled by default
        $checkButton->set_active(TRUE);

        my $button = $self->addButton(
            $grid,
            'Add',
            'Add the ' . $self->singular . ' to this cage',
            undef,
            1, 2, 11, 12,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my (
                $stimulus, $response, $interfaceName, $enabledFlag, $stimulusSwitch,
                $responseSwitch, $nameSwitch, $enabledSwitch, $splitterFlag, $splitterSwitch,
                $splitterString, $rewriterFlag, $rewriterSwitch, $rewriterString, $tempSwitch,
                $tempString, $result,
            );

            # Get the interface stimulus
            if ($self->checkEntryIcon($entry, $entry2, $entry3)) {

                $stimulus = $entry->get_text();             # Compulsory

                # Custom hook events must start with an underscore. Add one, if the user did not
                if (
                    $self->singular eq 'hook'
                    && ! $interfaceModelObj->ivExists('hookEventHash', $stimulus)
                    && ! ($stimulus =~ m/^\_/)
                ) {
                    $stimulus = '_' . $stimulus;
                }
            }

            if (defined $stimulus && $self->editObj->profName) {

                # Get the interface response and name
                $response = $entry2->get_text();            # Compulsory
                $interfaceName = $entry3->get_text();       # Optional
                if ($checkButton->get_active()) {
                    $enabledFlag = 1;
                } else {
                    $enabledFlag = 0;
                }

                $stimulusSwitch
                    = $self->interfaceModelObj->ivShow('compulsorySwitchHash', 'stimulus');
                $responseSwitch
                    = $self->interfaceModelObj->ivShow('compulsorySwitchHash', 'response');
                $nameSwitch
                    = $self->interfaceModelObj->ivShow('optionalSwitchHash', 'name');
                $enabledSwitch
                    = $self->interfaceModelObj->ivShow('optionalSwitchHash', 'enabled');

                if ($self->singular eq 'trigger') {

                    if ($checkButton2->get_active()) {      # Non-standard attribute
                        $splitterFlag = 1;
                    } else {
                        $splitterFlag = 0;
                    }

                    $splitterSwitch
                        = $self->interfaceModelObj->ivShow('optionalSwitchHash', 'splitter');
                    $splitterString = ' -' . $splitterSwitch . ' ' . $splitterFlag;

                    if ($checkButton3->get_active()) {      # Non-standard attribute
                        $rewriterFlag = 1;
                    } else {
                        $rewriterFlag = 0;
                    }

                    $rewriterSwitch
                        = $self->interfaceModelObj->ivShow('optionalSwitchHash', 'rewriter');
                    $rewriterString = ' -' . $rewriterSwitch . ' ' . $rewriterFlag;

                } else {

                    $splitterString = '';
                    $rewriterString = '';
                }

                if ($checkButton4->get_active()) {

                    $tempSwitch
                        = $self->interfaceModelObj->ivShow('optionalSwitchHash', 'temporary');
                    $tempString = ' -' . $tempSwitch . ' 1';

                } else {

                    $tempString = '';
                }

                if ($stimulus =~ m/[\<|\>]/ || $response =~ m/[\<|\>]/) {

                    $self->showMsgDialogue(
                        'Add ' . $self->singular,
                        'error',
                        'This version of ' . $axmud::SCRIPT . ' can\'t process diamond bracket'
                        . ' characters; please use . characters in your regular expressions'
                        . ' instead',
                        'ok',
                    );

                # Try to add a new trigger/alias/macro/timer/hook object belonging to this cage
                } elsif ($interfaceName) {

                    $result = $self->session->pseudoCmd(
                        'add' . $self->singular
                        . ' -' . $stimulusSwitch . ' <' . $stimulus
                        . '> -' . $responseSwitch . ' <' . $response
                        . '> -' . $enabledSwitch . ' ' . $enabledFlag
                        . $splitterString . $rewriterString . $tempString
                        . ' -' . $nameSwitch . ' <' . $interfaceName
                        . '> -d ' . $self->editObj->profName,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $result = $self->session->pseudoCmd(
                        'add' . $self->singular
                        . ' -' . $stimulusSwitch . ' <' . $stimulus
                        . '> -' . $responseSwitch . ' <' . $response
                        . '> -' . $enabledSwitch . ' ' . $enabledFlag
                        . $splitterString . $rewriterString . $tempString
                        . ' -d ' . $self->editObj->profName,
                        $self->pseudoCmdMode,
                    );
                }

                if ($result) {

                    # Refresh (both) lists
                    $self->interfaces1Tab_refreshList();
                    $self->interfaces2Tab_refreshList();
                }
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Edit...',
            'Edit the selected ' . $self->singular,
            undef,
            2, 3, 11, 12,
        );
        $button2->signal_connect('clicked' => sub {

            my ($interfaceName, $interfaceObj, $childWinObj);

            ($interfaceName) = $self->getSimpleListData($slWidget, 0);
            if (defined $interfaceName) {

                # Check that there's an interface with that name stored in this cage
                if ($self->editObj->ivExists('interfaceHash', $interfaceName)) {

                    # Open up an interface 'edit' window to edit the (inactive) interface
                    $interfaceObj = $self->editObj->ivShow('interfaceHash', $interfaceName);

                    $childWinObj = $self->createFreeWin(
                        'Games::Axmud::EditWin::Interface::' . ucfirst($self->singular),
                        $self,
                        $self->session,
                        'Edit ' . $self->singular . ' interface \'' . $interfaceName . '\'',
                        $interfaceObj,
                        FALSE,                          # Not temporary
                    );

                    if ($childWinObj) {

                        # When the 'edit' window closes, update widgets and/or IVs
                        $self->add_childDestroy(
                            $childWinObj,
                            'interfaces1Tab_refreshList',
                            [],         # No arguments required
                        );

                        $self->add_childDestroy(
                            $childWinObj,
                            'interfaces2Tab_refreshList',
                            [],         # No arguments required
                        );
                    }
                }
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Delete',
            'Delete the selected ' . $self->singular,
            undef,
            3, 4, 11, 12,
            TRUE,               # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($interfaceName, $profName);

            ($interfaceName) = $self->getSimpleListData($slWidget, 0);
            $profName = $self->editObj->profName;

            if (defined $interfaceName && defined $profName) {

                # Check that there's an interface with that name stored in this cage
                if ($self->editObj->ivExists('interfaceHash', $interfaceName)) {

                    # Delete the interface
                    $self->session->pseudoCmd(
                        'delete' . $self->singular . ' ' . $interfaceName . ' -d ' . $profName,
                        $self->pseudoCmdMode,
                    );

                    # Refresh (both) lists
                    $self->interfaces1Tab_refreshList();
                    $self->interfaces2Tab_refreshList();
                }
            }
        });

        my $button4 = $self->addButton(
            $grid,
            'Export',
            'Export selected ' . $self->plural . ' to the interface clipboard',
            undef,
            4, 6, 11, 12,
            TRUE,               # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my (
                $profName,
                @interfaceList,
            );

            @interfaceList = $self->getSimpleListData($slWidget, 0);
            $profName = $self->editObj->profName;

            if (@interfaceList && defined $profName) {

                # Empty the clipboard
                $self->session->pseudoCmd(
                    'clearclipboard',
                    $self->pseudoCmdMode,
                );

                # Export the selected interfaces
                foreach my $interfaceName (@interfaceList) {

                    $self->session->pseudoCmd(
                        'export' . $self->singular . ' ' . $interfaceName . ' -d ' . $profName,
                        $self->pseudoCmdMode,
                    );
                }

                # Refresh (both) lists
                $self->interfaces1Tab_refreshList();
                $self->interfaces2Tab_refreshList();
            }
        });

        my $button5 = $self->addButton(
            $grid,
            'Import',
            'Import all ' . $self->plural . ' from the interface clipboard',
            undef,
            6, 8, 11, 12,
            TRUE,               # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            my $profName = $self->editObj->profName;

            if (defined $profName) {

                # Import all interfaces of the correct category (triggers, etc)
                $self->session->pseudoCmd(
                    'import' . $self->singular . ' -d ' . $profName,
                    $self->pseudoCmdMode,
                );

                # Refresh (both) lists
                $self->interfaces1Tab_refreshList();
                $self->interfaces2Tab_refreshList();
            }
        });

        my $button6 = $self->addButton(
            $grid,
            'Dump',
            'Display this list of ' . $self->plural . ' in the \'main\' window',
            undef,
            8, 10, 11, 12,
        );
        $button6->signal_connect('clicked' => sub {

            my $profName = $self->editObj->profName;
            if ($profName) {

                # Display the data
                $self->session->pseudoCmd(
                    'list' . $self->singular . ' -d ' . $profName,
                    $self->pseudoCmdMode,
                );

                # Refresh (both) lists
                $self->interfaces1Tab_refreshList();
                $self->interfaces2Tab_refreshList();
            }
        });

        my $button7 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of ' . $self->plural,
            undef,
            10, 12, 11, 12,
        );
        $button7->signal_connect('clicked' => sub {

            # Refresh (both) lists
            $self->interfaces1Tab_refreshList();
            $self->interfaces2Tab_refreshList();
        });

        if ($self->editObj->cageType eq 'alias') {

            my $button6 = $self->addButton(
                $grid,
                'Recommended: add ^...$',
                'Adds symbols to match the pattern against the whole world command',
                undef,
                9, 12, 8, 9,
            );

            $button6->signal_connect('clicked' => sub {

                my $string = $entry->get_text();

                if ($self->checkEntryIcon($entry)) {

                    $string = $entry->get_text();
                    if (! ($string =~ m/^\^/)) {

                        $string = '^' . $string;
                    }

                    if (! ($string =~ m/\$$/)) {

                        $string .= '$';
                    }

                    $entry->set_text($string);
                }
            });
        }

        return 1;
    }

    sub interfacesTab_checkInterval {

        # Called by $self->interfacesTab_addButtons to check a timer interval is valid
        #
        # Expected arguments
        #   $value      - The value to check, should be a number (minimum value 0.01), or a 24-hour
        #                   clock time in the form HH::MM
        #
        # Return values
        #   'undef' on improper arguments or if $value is invalid
        #   1 if $value is valid

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

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

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

        if (
            ! $axmud::CLIENT->floatCheck($value, 0.1)
            && ! ($value =~ m/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/)
            && ! ($value =~ m/^99\:[0-5][0-9]$/)
        ) {
            return undef;
        } else {
            return 1;
        }
    }

    sub interfacesTab_checkHookEvent {

        # Called by $self->interfacesTab_addButtons to check a hook event is valid
        #
        # Expected arguments
        #   $value  - The value to check, should be a standard hook event, or a custom event whose
        #               name starts with an underline, followed by an alphanumeric character
        #
        # Return values
        #   'undef' on improper arguments or for an invalid hook event
        #   1 for a valid hook event

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

        # Local variables
        my $interfaceModelObj;

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

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

        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', 'hook');

        # Custom hook events must begin with an underline, followed by an alphanumeric character
        # If the user doesn't type an underline, the 'Add' button will add one
        if (
            ! $interfaceModelObj->ivExists('hookEventHash', $value)
            && ! ($value =~ m/^\_[A-Za-z0-9]/)
            && ! ($value =~ m/^[A-Za-z0-9]/)
        ) {
            return undef;
        } else {
            return 1;
        }
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    sub slWidget1
        { $_[0]->{slWidget1} }
    sub slWidget2
        { $_[0]->{slWidget2} }
    sub singular
        { $_[0]->{singular} }
    sub plural
        { $_[0]->{plural} }
    sub interfaceModelObj
        { $_[0]->{interfaceModelObj} }
    sub stimulus
        { $_[0]->{stimulus} }
    sub response
        { $_[0]->{response} }
}

{ package Games::Axmud::EditWin::Interface::Active;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Interface::Active')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            [
                'Settings',
                'Standard attributes',
                'Independent interface IVs',
                'Dependent interface IVs',
            ],
        );

        # Unusual step - create a hash IV for all 'edit' windows inheriting this one. Each key-value
        #   pair corresponds to a key-value pair in GA::Interface::Trigger->attribHash (etc)
        $self->{attribHash} = {};

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $func;

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

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

        $func = $self->editObj->category . 'AttributesTab';     # e.g. ->triggerAttributesTab
        $self->$func();

        $self->propertiesTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave. Function based on
        #   GA::EditWin::Generic::Interface->saveChanges
        # Saves any changes made to data stored by the edit object (which can be three of the four
        #   standard attributes, 'stimulus', 'response' and 'enabled'). Also saves any modified
        #   attributes stored in $self->attribHash
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my %attribHash;

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

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

        if ($self->editHash || $self->attribHash) {

            # Transfer the modified non-standard attributes $self->attribHash to the local variable
            #   %attribHash. At the same time, deal with any 'dummy' attributes stored in
            #   $self->attribHash
            foreach my $attrib ($self->ivKeys('attribHash')) {

                my $value = $self->ivShow('attribHash', $attrib);

                # Attributes beginning with an underline are 'dummy' attributes, used to help set
                #   the values of other attributes (e.g. '_substr_num', used to set 'style_mode')
                if (substr($attrib, 0, 1) ne '_') {

                    if ($attrib eq 'style_mode' && $value == -3) {

                        # The attribute's true value is stored in the dummy attribute, '_substr_num'
                        $attribHash{$attrib} = $self->ivShow('attribHash', '_substr_num');

                    } else {

                        # Normal attribute
                        $attribHash{$attrib} = $value;
                    }
                }
            }

            # Add any modified standard attributes to %attribHash. $self->editHash can contain the
            #   keys 'stimulus', 'response' and 'enabled'
            foreach my $attrib ($self->ivKeys('editHash')) {

                if ($attrib eq 'stimulus' || $attrib eq 'response') {
                    $attribHash{$attrib} = $self->ivShow('editHash', $attrib);
                } elsif ($attrib eq 'enabledFlag') {
                    $attribHash{'enabled'} = $self->ivShow('editHash', 'enabledFlag');
                }
            }

            # Store the modified standard and non-standard attributes
            $self->editObj->modifyAttribs($self->session, %attribHash);

            # The local hashes can now be emptied
            $self->ivEmpty('editHash');
            $self->ivEmpty('attribHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $interfaceModelObj,
            @comboList,
        );

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

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

        # Tab setup (already created by the calling function)

        # Settings (left column)
        $self->addLabel($grid, '<b>Settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 12, 1, 2);
        $self->addLabel($grid, 'Number',
            1, 3, 2, 3);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Category',
            1, 3, 3, 4);
        $self->addEntry($grid, 'category', FALSE,
            3, 6, 3, 4);

        # (Right column)
        $self->addLabel($grid, '(Inactive) parent',
            7, 9, 2, 3);
        my $entry = $self->addEntry($grid, undef, FALSE,
            9, 12, 2, 3);
        if ($self->editObj->parent) {
            $entry->set_text($self->editObj->parent->name);
        } else {
            $entry->set_text('n/a');
        }

        $self->addLabel($grid, 'Type',
            7, 9, 3, 4);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            9, 12, 3, 4);
        if ($self->editObj->indepFlag) {
            $entry2->set_text('independent');
        } else {
            $entry2->set_text('dependent');
        }

        # Standard attributes
        $self->addLabel($grid, '<b>Standard attributes</b>',
            0, 12, 4, 5);
        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', $self->editObj->category);

        $self->addLabel($grid, 'Stimulus <i>(' . $interfaceModelObj->stimulusName . ')</i>',
            1, 3, 5, 6);
        # For hooks, use a combo; for everything else, use an entry
        if ($self->editObj->category eq 'trigger' || $self->editObj->category eq 'alias') {

            $self->addEntryWithIcon($grid, 'stimulus', 'regex', 1, undef,
                3, 12, 5, 6);

        } elsif ($self->editObj->category eq 'macro') {

            $self->addEntryWithIcon($grid, 'stimulus', 'string', 1, undef,
                3, 12, 5, 6);

        } elsif ($self->editObj->category eq 'timer') {

            $self->addEntryWithIcon($grid, 'stimulus', 'float', 0.01, undef,
                3, 12, 5, 6);

        } elsif ($self->editObj->category eq 'hook') {

            @comboList = sort {$a cmp $b} ($interfaceModelObj->ivKeys('hookEventHash'));
            $self->addComboBox($grid, 'stimulus', \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 5, 6);
        }

        $self->addLabel($grid, 'Response <i>(' . $interfaceModelObj->responseName . ')</i>',
            1, 3, 6, 7);
        $self->addEntryWithIcon($grid, 'response', 'string', 1, undef,
            3, 12, 6, 7);
        $self->addCheckButton($grid, 'Enabled', 'enabledFlag', TRUE,
            1, 12, 7, 8);

        # Independent interface IVs
        $self->addLabel($grid, '<b>Independent interface IVs</b>',
            0, 6, 8, 9);
        $self->addLabel($grid, 'Associated profile',
            1, 3, 9, 10);
        $self->addEntry($grid, 'assocProf', FALSE,
            3, 6, 9, 10);
        $self->addLabel($grid, 'Profile category',
            1, 3, 10, 11);
        $self->addEntry($grid, 'assocProfCategory', FALSE,
            3, 6, 10, 11);

        # Dependent interface IVs
        $self->addLabel($grid, '<b>Dependent interface IVs</b>',
            7, 12, 8, 9);
        $self->addLabel($grid, 'Call class',
            7, 9, 9, 10);
        $self->addEntry($grid, 'callClass', 0,
            9, 12, 9, 10);
        $self->addLabel($grid, '(...named)',
            7, 9, 10, 11);
        my $entry3 = $self->addEntry($grid, undef, 0,
            9, 12, 10, 11);
        if (defined $self->editObj->callClass) {

            $entry3->set_text($self->editObj->callClass->_objName);
        }
        $self->addLabel($grid, 'Call method',
            7, 9, 11, 12);
        $self->addEntry($grid, 'callMethod', 0,
            9, 12, 11, 12);

        # Tab complete
        return 1;
    }

    sub attributesTab {

        # Attributes tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Attributes');

        # Attributes
        $self->addLabel($grid, '<b>Attributes</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of attributes for this active interface</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Attribute', 'text',
            'Current value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'attribHash');

        # Add a single button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of attributes',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'attribHash');
        });

        # Tab complete
        return 1;
    }

    sub propertiesTab {

        # Properties tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Properties');

        # Properties
        $self->addLabel($grid, '<b>Properties</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of custom properties for this active interface</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'propertyHash');

        # Add a single button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of properties',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'propertyHash');
        });

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    sub attribHash
        { my $self = shift; return %{$self->{attribHash}}; }
}

{ package Games::Axmud::EditWin::Interface::Trigger;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Interface Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Interface::Trigger')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->triggerAttributesTab();
        $self->beforeAfterTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Interface::Alias;
    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Interface Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Interface::Alias')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->aliasAttributesTab();
        $self->beforeAfterTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Interface::Macro;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Interface Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Interface::Macro')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->macroAttributesTab();
        $self->beforeAfterTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Interface::Timer;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Interface Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Interface::Timer')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->timerAttributesTab();
        $self->beforeAfterTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Interface::Hook;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Interface Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Interface::Hook')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->hookAttributesTab();
        $self->beforeAfterTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::InterfaceModel;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Generic::InterfaceModel')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

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

        # Local variables
        my @emptyList;

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

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

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Category',
            ['Interface category', ucfirst($self->editObj->category) . ' attributes'],
        );

        # Set up the rest of the tab
        $self->categoryTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->attributesTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub categoryTab {

        # Category tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @eventList;

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>Interface category</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Category',
            1, 3, 1, 2);
        $self->addEntry($grid, 'category', FALSE,
            3, 6, 1, 2);

        # Right column
        $self->addLabel($grid, '<b>' . ucfirst($self->editObj->category) . ' attributes</b>',
            7, 12, 0, 1);
        $self->addLabel($grid, 'Stimulus name',
            7, 9, 1, 2);
        $self->addEntry($grid, 'stimulusName', FALSE,
            9, 12, 1, 2);
        $self->addLabel($grid, 'Response name',
            7, 9, 2, 3);
        $self->addEntry($grid, 'responseName', FALSE,
            9, 12, 2, 3);

        # Hook events (for the hook interface model object only)
        if ($self->editObj->category eq 'hook') {

            # List of hook events
            $self->addLabel($grid, '<b>List of hook events</b>',
                0, 12, 3, 4);
            my $textView = $self->addTextView($grid, undef, FALSE,
               1, 12, 4, 12);

            @eventList = sort {$a cmp $b} ($self->editObj->ivKeys('hookEventHash'));

            my $buffer = $textView->get_buffer();
            $buffer->set_text(join("\n", @eventList));
        }

        # Tab complete
        return 1;
    }

    sub attributesTab {

        # Attributes tab
        #
        # 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 . '->attributesTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Attributes');

        # Add tabs to the inner notebook
        $self->attributes1Tab($innerNotebook);
        $self->attributes2Tab($innerNotebook);
        $self->attributes3Tab($innerNotebook);
        $self->attributes4Tab($innerNotebook);

        return 1;
    }

    sub attributes1Tab {

        # Attributes1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Attribute types'],
        );

        # Attribute types
        $self->addLabel($grid, '<b>Attribute types</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Acceptable values for all attributes (standard and '
            . $self->editObj->category . ' attributes)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Attribute', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'attribTypeHash');

        # Tab complete
        return 1;
    }

    sub attributes2Tab {

        # Attributes2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Default values'],
        );

        # Default values
        $self->addLabel($grid, '<b>Default values</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Default values for ' . $self->editObj->category
            . ' attributes</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Attribute', 'text',
            'Default value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'optionalAttribHash');

        # Tab complete
        return 1;
    }

    sub attributes3Tab {

        # Attributes3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Compulsory switches'],
        );

        # Compulsory switches
        $self->addLabel($grid, '<b>Compulsory switches</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of compulsory switches for the \'' . $axmud::CLIENT->cmdSep
            . 'add' . $self->editObj->category . '\' command</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Attribute', 'text',
            'Switch', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'compulsorySwitchHash');

        # Tab complete
        return 1;
    }

    sub attributes4Tab {

        # Attributes4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Optional switches'],
        );

        # Optional switches
        $self->addLabel($grid, '<b>Optional switches</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of optional switches for the \'' . $axmud::CLIENT->cmdSep
            . 'add' . $self->editObj->category . '\' command</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Attribute', 'text',
            'Switch', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'optionalSwitchHash');

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::MapLabelStyle;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::MapLabelStyle')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Map label style'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # If the style name has been changed, apply that change with a client command
            if ($self->ivExists('editHash', 'name')) {

                if (
                    ! $self->session->pseudoCmd(
                        'renamelabelstyle <' . $self->editObj->name . '> <'
                        . $self->ivShow('editHash', 'name'),
                    )
                ) {

                    # Unable to save changes; notify the user
                    $self->showMsgDialogue(
                        'Rename label style',
                        'error',
                        'The label style could not be renamed - save aborted',
                        'ok',
                    );

                    return undef;

                } else {

                    $self->ivDelete('editHash', 'name');
                }
            }

            # Store any remaining changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Redraw labels in maps in all Automapper windows using this world model
            $self->session->worldModelObj->updateMapLabels();
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $count, $index, $angleFlag,
            @list, @comboList,
            %descripHash, %revDescripHash,
        );

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

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

        # Tab setup (already created by the calling function)

        # Map label style
        $self->addLabel($grid, '<b>Map label style</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Style name (max 16 chars)',
            1, 4, 1, 2);
        $self->addEntryWithIcon($grid, 'name', 'string', 1, 16,
            4, 8, 1, 2,
            16, 16);

        $self->addLabel($grid, 'Text colour',
            1, 4, 2, 3);
        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($grid,
            $self->editObj->textColour,
            undef,                          # No neutral colour
            4, 5, 2, 3);
        my $entry = $self->addEntry($grid, 'textColour', FALSE,
            5, 6, 2, 3,
            7, 7);
        my $button = $self->addButton($grid, 'Set', 'Set this colour', undef,
            6, 7, 2, 3);
        $button->signal_connect('clicked' => sub {

            # Prompt the user to select a new colour, using the existing colour as an initial value
            my $rgbModify = $self->showColourSelectionDialogue(
                'Set text colour',
                $self->editObj->textColour,
            );

            if ($rgbModify) {

                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $rgbModify);
                $entry->set_text($rgbModify);

                # Update IVs
                $self->ivAdd('editHash', 'textColour', $rgbModify);
            }
        });
        my $button2 = $self->addButton($grid, 'Reset', 'Reset this colour', undef,
            7, 8, 2, 3);
        $button2->signal_connect('clicked' => sub {

            $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj);
            $entry->set_text('');

            # Update IVs
            $self->ivAdd('editHash', 'textColour', undef);
        });

        $self->addLabel($grid, 'Underlay colour',
            1, 4, 3, 4);
        my ($frame2, $canvas2, $canvasObj2) = $self->addSimpleCanvas($grid,
            $self->editObj->underlayColour,
            undef,                          # No neutral colour
            4, 5, 3, 4);
        my $entry2 = $self->addEntry($grid, 'underlayColour', FALSE,
            5, 6, 3, 4,
            7, 7);
        my $button3 = $self->addButton($grid, 'Set', 'Set this colour', undef,
            6, 7, 3, 4);
        $button3->signal_connect('clicked' => sub {

            # Prompt the user to select a new colour, using the existing colour as an initial value
            my $rgbModify = $self->showColourSelectionDialogue(
                'Set underlay colour',
                $self->editObj->textColour,
            );

            if ($rgbModify) {

                $canvasObj2 = $self->fillSimpleCanvas($canvas2, $canvasObj2, $rgbModify);
                $entry2->set_text($rgbModify);

                # Update IVs
                $self->ivAdd('editHash', 'underlayColour', $rgbModify);
            }
        });
        my $button4 = $self->addButton($grid, 'Reset', 'Reset this colour', undef,
            7, 8, 3, 4);
        $button4->signal_connect('clicked' => sub {

            $canvasObj2 = $self->fillSimpleCanvas($canvas2, $canvasObj2);
            $entry2->set_text('');

            # Update IVs
            $self->ivAdd('editHash', 'underlayColour', undef);
        });

        $self->addLabel($grid, 'Relative size (0.5 - 10)',
            1, 4, 4, 5);
        $self->addEntryWithIcon($grid, 'relSize', 'float', 0.5, 10,
            4, 6, 4, 5,
            8, 8);

        # Orientation
        $self->addLabel($grid, 'Orientation',
            1, 4, 5, 6);

        $count = -1;
        @list = (
            0 => 'Normal orientation',
            180 => 'Rotated 180 degrees',
            90  => 'Rotated 90 degrees clockwise',
            270  => 'Rotated 90 degrees anti-clockwise',
            -1 => 'Custom orientation (set below)',
        );

        do {

            my $angle = shift @list;
            my $descrip = shift @list;

            push (@comboList, $descrip);
            $descripHash{$descrip} = $angle;
            $revDescripHash{$angle} = $descrip;

            $count++;
            if ($angle == $self->editObj->rotateAngle) {

                $index = $count;
                $angleFlag = TRUE;
            }

        } until (! @list);

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            4, 8, 5, 6,
        );
        # ->signal_connect appears below

        $self->addLabel($grid, 'Angle (0-359)',
            1, 4, 6, 7);

        my $entry3 = $self->addEntryWithIcon($grid, 'rotateAngle', 'int', 0, 359,
            4, 6, 6, 7,
            3, 3);

        if ($angleFlag) {

            $combo->set_active($index);
            $entry3->set_sensitive(FALSE);

        } else {

            $combo->set_active($count);
            $entry3->set_sensitive(TRUE);
        }

        # ->signal_connect from above
        $combo->signal_connect('changed' => sub {

            my $descrip = $combo->get_active_text();

            if ($descrip eq $revDescripHash{-1}) {

                $entry3->set_sensitive(TRUE);
                $entry3->set_text(0);

            } else {

                $entry3->set_sensitive(FALSE);
                $entry3->set_text($descripHash{$descrip});
            }
        });

        # Right column
        $self->addCheckButton($grid, 'Italics', 'italicsFlag', TRUE,
            9, 12, 1, 2);
        $self->addCheckButton($grid, 'Bold', 'boldFlag', TRUE,
            9, 12, 2, 3);
        $self->addCheckButton($grid, 'Underline', 'underlineFlag', TRUE,
            9, 12, 3, 4);
        $self->addCheckButton($grid, 'Strikethrough', 'strikeFlag', TRUE,
            9, 12, 4, 5);
        $self->addCheckButton($grid, 'Draw box around label', 'boxFlag', TRUE,
            9, 12, 5, 6);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Mission;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Mission')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->contentsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Name
        $self->addLabel($grid, '<b>Name</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 4, 1, 2);
        $self->addEntry($grid, 'name', 0,
            4, 6, 1, 2);
        $self->addLabel($grid, 'Description',
            1, 4, 2, 3);
        $self->addEntryWithIcon($grid, 'descrip', 'string', 1, 64,
            4, 12, 2, 3);

        # Tab complete
        return 1;
    }

    sub contentsTab {

        # Contents tab
        #
        # 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 . '->contentsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Contents',
            ['Contents list'],
        );

        # Contents list
        $self->addLabel($grid, '<b>Contents list</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>The contents of the mission; each line is a separate instruction</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'missionList', TRUE,
            1, 12, 4, 7,
            TRUE, FALSE, FALSE, FALSE,   # Treat as a list, don't remove empty lines or whitespace
        );

        $self->addLabel($grid, '<b>Commands:</b> ',
            1, 4, 7, 8);
        $self->addLabel($grid,
            '<b>></b> <i>world command,</i> '
            . '<b>;</b> <i>client command,</i> '
            . '<b>.</b> <i>speedwalk command,</i> '
            . '<b>#</b> <i>\'main\' window comment</i>',
            4, 12, 7, 8);

        $self->addLabel($grid, '<b>Breaks:</b> ',
            1, 4, 8, 9);
        $self->addLabel($grid,
            '<b>@</b> <i>ordinary break,</i> '
            . '<b>t {pattern}</b> <i>trigger break,</i> '
            . '<b>p {interval}</b> <i>pause break,</i> '
            . '<b>l</b> <i>Locator break</i>',
            4, 12, 8, 9);

        $self->addLabel($grid, '<b>Automated logins:</b> ',
            1, 4, 9, 10);
        $self->addLabel($grid,
            '<b>n</b> <i>send current character\'s name,</i> '
            . '<b>w</b> <i>send password,</i> '
            . '<b>a</b> <i>send account name,</i>',
            4, 12, 9, 10);
        $self->addLabel($grid,
            '<b>c</b> <i>send combined command</i>',
            4, 12, 10, 11);
        $self->addLabel($grid,
            '<i>(in combined commands, </i><b>@name@</b><i>, </i><b>@password@</b><i> and'
            . ' </i><b>@account@</b><i> are substituted)</i>',
            4, 12, 11, 12);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Generic::ModelObj;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

#   sub checkEditObj {}         # Inherited from GA::Generic::ConfigWin

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['General properties', 'Source code path'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 4
        $self->physicalTab();
        $self->exclusiveTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves any changes made to data stored by the edit object
        # (We don't use the inherited function, because for region and room objects, we need to
        #   tell Automapper windows to update themselves - even if $self->editHash is empty)
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        # For objects stored in the world model (first checking that they are still there)...
        if (
            $self->editObj->modelFlag
            && $self->session->worldModelObj->ivExists('modelHash', $self->editObj->number)
            && $self->session->worldModelObj->ivShow('modelHash', $self->editObj->number)
                eq $self->editObj
        ) {
            # For regions and rooms, update any Automapper windows
            if ($self->editObj->category eq 'region') {

                # Redraw this region, if drawn
                $self->session->worldModelObj->updateRegion($self->editObj->name);

            } elsif ($self->editObj->category eq 'room') {

                # Mark this room to be redrawn, if it is visible
                $self->session->worldModelObj->updateMaps('room', $self->editObj);
            }
        }

        return 1;
    }

    # Notebook tabs used by more than one model object 'edit' window

    # Group 1

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>General properties</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Category',
            1, 3, 2, 3);
        $self->addEntry($grid, 'category', FALSE,
            3, 6, 2, 3);
        $self->addCheckButton($grid, 'In world model', 'modelFlag', FALSE,
            1, 6, 3, 4);
        $self->addLabel($grid, 'World model #',
            1, 3, 4, 5);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 4, 5);

        # Source code path
        $self->addLabel($grid, '<b>Source code path</b>',
            0, 12, 6, 7);
        if ($self->session->worldModelObj->mudlibPath) {

            $self->addLabel($grid,
                '<i>The equivalent file in the mudlib, relative to '
                . $self->session->worldModelObj->mudlibPath . '</i>',
                1, 12, 7, 8);

        } else {

            $self->addLabel($grid,
                '<i>The equivalent file in the mudlib, e.g.'
                . ' /home/myname/ds/lib/domains/town/room/vill_road2</i>',
                1, 12, 7, 8);
        }

        my $entry = $self->addEntry($grid, 'sourceCodePath', TRUE,
            1, 12, 8, 9);

        my $button = $self->addButton($grid,
            'View',
            'View this file',
            undef,
            1, 3, 9, 10);
        $button->signal_connect('clicked' => sub {

            my $flag;

            if ($self->getEditHash_scalarIV('virtualAreaPath')) {
                $flag = TRUE;
            } else {
                $flag = FALSE;
            }

            $self->quickFreeWin(
                'Games::Axmud::OtherWin::SourceCode',
                $self->session,
                # Config
                'model_obj' => $self->editObj,
                'virtual_flag' => $flag,
                'path' => $self->getEditHash_scalarIV('sourceCodePath'),
            );
        });

        my $button2 = $self->addButton($grid,
            'Edit',
            'Edit this file',
            undef,
            3, 5, 9, 10);
        $button2->signal_connect('clicked' => sub {

            my ($cmd, $file);

            # Check that the GA::Client has a text editor command set, and that it is valid
            $cmd = $axmud::CLIENT->textEditCmd;
            if (! $cmd || ! ($cmd =~ m/%s/)) {

                # Show a 'dialogue' window to explain the problem
                $self->showMsgDialogue(
                    'Edit source code file',
                    'error',
                    'Can\'t edit the file: invalid external application command \'' . $cmd . '\'',
                    'ok',
                );

                return undef;
            }

            # Set the file to be opened. If the current world model defines a mudlib directory, the
            #   object's ->mudlibPath is relative to that; otherwise it's an absolute path
            if ($self->session->worldModelObj->mudlibPath) {
                $file = $self->session->worldModelObj->mudlibPath;
            } else {
                $file = '';
            }

            if ($self->getEditHash_scalarIV('virtualAreaPath')) {
                $file .= $self->getEditHash_scalarIV('virtualAreaPath');
            } else {
                $file .= $self->getEditHash_scalarIV('sourceCodePath');
            }

            # Add the file extension, if set
            if ($self->session->worldModelObj->mudlibExtension) {

                $file .= $self->session->worldModelObj->mudlibExtension;
            }

            # Check the file exists
            if (! (-e $file)) {

                $self->showMsgDialogue(
                    'Edit source code file',
                    'error',
                    'Can\'t find the file \'' . $file . '\'',
                    'ok',
                );

                return undef;
            }

            # Open the file in the external text editor
            $cmd =~ s/%s/$file/;

            system $cmd;
        });

        # This IV only available to rooms
        if ($self->editObj->category eq 'room') {

            $self->addLabel($grid, '<i>For rooms in virtual areas, the virtual area\'s file</i>',
                1, 12, 10, 11);
            my $entry2 = $self->addEntry($grid, 'virtualAreaPath', TRUE,
                1, 12, 11, 12);
            # 2nd entry box starts insensitive if the 1st entry box doesn't contain text
            if (! $entry->get_text()) {

                $entry2->set_sensitive(FALSE);
            }

            my $button3 = $self->addButton($grid,
                'Copy to virtual path',
                'Copy the file path above into the entry box below',
                undef,
                7, 9, 9, 10);
            $button3->signal_connect('clicked' => sub {

                # Copy the contents of the first entry into the second when the button is clicked
                $entry2->set_text($entry->get_text());
            });

            # The second entry can only be edited when the first contains text
            $entry->signal_connect('changed' => sub {

                # Update the IV
                my $text = $entry->get_text();
                $self->ivAdd('editHash', 'sourceCodePath', $text);

                if (! $entry->get_text()) {

                    # First entry is now empty, so empty the second entry too
                    $entry2->set_text('');
                    $entry2->set_sensitive(FALSE);

                } else {

                    # Sensitise the second entry
                    $entry2->set_sensitive(TRUE);
                }
            });
        }

        # Right column
        $self->addCheckButton($grid, 'Concrete (not abstract) object', 'concreteFlag', FALSE,
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Alive', 'aliveFlag', FALSE,
            7, 12, 2, 3);
        $self->addCheckButton($grid, 'Sentient', 'sentientFlag', FALSE,
            7, 12, 3, 4);
        $self->addCheckButton($grid, 'Portable (can be carried)', 'portableFlag', FALSE,
            7, 12, 4, 5);
        $self->addCheckButton($grid, 'Saleable (can be sold)', 'saleableFlag', FALSE,
            7, 12, 5, 6);

        # Tab complete
        return 1;
    }

    sub familyTab {

        # Family tab
        #
        # 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 . '->familyTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Family');

        # Add tabs to the inner notebook
        $self->family1Tab($innerNotebook);
        $self->family2Tab($innerNotebook);

        return 1;
    }

    sub family1Tab {

        # Family1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Parent object'],
        );

        # Parent object
        $self->addLabel($grid, '<b>Parent object</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List containing the world model object which \'owns\' this one (if any)</i>',
            1, 12, 1, 2);

        # Add a simple list - it never contains more than one object, but we use a simple list to
        #   keep this tab consistent with the next one
        @columnList = (
            'Model #', 'int',
            'Object', 'text',
            'Object name', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->family1Tab_refreshList($slWidget, (scalar @columnList / 2));

        # All of the buttons (except 'Edit') are not available when $self->editObj is a region or
        #   a room
        my $entry;

        if ($self->editObj->category ne 'region' && $self->editObj->category ne 'room') {

            $self->addLabel($grid, 'Object #:',
                1, 4, 10, 11);

            my $funcRef = sub {

                # Check whether a valid world model object number has been entered - also, a world
                #   model object can't add itself as its own parent/child add
                my $value = $entry->get_text();

                if (
                    $self->session->worldModelObj->ivExists('modelHash', $value)
                    && $value ne $self->editObj->number
                ) {
                    return 1;       # Valid
                } else {
                    return undef;   # Invalid
                }
            };
            $entry = $self->addEntryWithIcon($grid, undef, $funcRef, undef, undef,
                4, 6, 10, 11);

            my $button = $self->addButton($grid,
                'Set as parent', 'Set this world model object as the parent', undef,
                6, 9, 10, 11,
                TRUE);      # Irreversible
            $button->signal_connect('clicked' => sub {

                my $value;

                if ($self->checkEntryIcon($entry)) {

                    $value = $entry->get_text();

                    # Attempt to change the parent
                    if (
                        ! $self->session->worldModelObj->setParent(
                            TRUE,           # Update Automapper windows
                            $self->editObj->number,
                            $value,
                        )
                    ) {
                        $self->showMsgDialogue(
                            'Add model object',
                            'error',
                            'Failed to set the parent object',
                            'ok',
                        );

                    } else {

                        # Update the simple list
                        $self->family1Tab_refreshList($slWidget, (scalar @columnList / 2));
                    }

                    # In either case, reset the entry box
                    $self->resetEntryBoxes($entry);
                }
            });
        }

        my $button2 = $self->addButton(
            $grid, 'Edit...', 'Edit the selected world model object', undef,
            9, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number && $self->session->worldModelObj->ivExists('modelHash', $number)) {

                # Open up an 'edit' window for the child object
                $self->openChildEditWin($number);

                # Refresh the simple lists and reset entry boxes
                $self->family1Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        if ($self->editObj->category ne 'region' && $self->editObj->category ne 'room') {

            my $button3 = $self->addButton($grid,
                'Remove parent', 'Remove the selected world model object from the list', undef,
                1, 4, 11, 12,
                TRUE);      # Irreversible
            $button3->signal_connect('clicked' => sub {

                my ($number) = $self->getSimpleListData($slWidget, 0);
                if (
                    defined $number
                    && $self->session->worldModelObj->ivExists('modelHash', $number)
                ) {
                    # Attempt to reset the parent
                    if (
                        ! $self->session->worldModelObj->setParent(
                            TRUE,           # Update Automapper windows
                            $self->editObj->number,
                            undef,          # Object now has no parent
                        )
                    ) {
                        $self->showMsgDialogue(
                            'Add model object',
                            'error',
                            'Failed to reset the parent object',
                            'ok',
                        );

                    } else {

                        # Update the simple list
                        $self->family1Tab_refreshList($slWidget, (scalar @columnList / 2));
                    }

                    # In either case, reset the entry box
                    $self->resetEntryBoxes($entry);
                }
            });
        }

        # Tab complete
        return 1;
    }

    sub family2Tab {

        # Family2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Child objects'],
        );

        # Child objects
        $self->addLabel($grid, '<b>Child objects</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid, '<i>List of world model objects which are \'owned\' by this one</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Model #', 'int',
            'Category', 'text',
            'Object name', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->family2Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing buttons
        $self->addLabel($grid, 'Object #:',
            1, 4, 10, 11);

        my $entry;
        my $funcRef = sub {

            # Check whether a valid world model object number has been entered - also, a world model
            #   object can't add itself as its own parent/child add
            my $value = $entry->get_text();

            if (
                $self->session->worldModelObj->ivExists('modelHash', $value)
                && $value ne $self->editObj->number
            ) {
                return 1;       # Valid
            } else {
                return undef;   # Invalid
            }
        };
        $entry = $self->addEntryWithIcon($grid, undef, $funcRef, undef, undef,
            4, 6, 10, 11);

        my $button = $self->addButton($grid,
            'Add as child', 'Add the specified model object as a child of this object', undef,
            6, 9, 10, 11,
            TRUE);      # Irreversible
        $button->signal_connect('clicked' => sub {

            my ($number, $childObj);

            if ($self->checkEntryIcon($entry)) {

                $number = $entry->get_text();

                # If the edited object is a region or a room, we can't add a region or a room as a
                #   child from this window. The call to ->addChild already checks for this, but we
                #   can provide an informative 'dialogue' window to explain the problem
                $childObj = $self->session->worldModelObj->ivShow('modelHash', $number);
                if (
                    (
                        $self->editObj->category eq 'region'
                        && ($childObj->category eq 'region' || $childObj->category eq 'room')
                    ) || (
                        $self->editObj->category eq 'room'
                        && ($childObj->category eq 'region' || $childObj->category eq 'room')
                    )
                ) {
                    $self->showMsgDialogue(
                        'Add model object',
                        'warning',
                        'Regions and rooms cannot be given a child region/room from this tab',
                        'ok',
                    );

                # Otherwise, attempt to add a child
                } elsif (
                    ! $self->session->worldModelObj->addChild(
                        TRUE,           # Update Automapper windows
                        $self->editObj->number,
                        $number,
                    )
                ) {
                    $self->showMsgDialogue(
                        'Add model object',
                        'error',
                        'Failed to add a child object',
                        'ok',
                    );

                } else {

                    # Update the simple list
                    $self->family2Tab_refreshList($slWidget, (scalar @columnList / 2));
                }

                # In either case, reset the entry box
                $self->resetEntryBoxes($entry);
            }
        });

        my $button2 = $self->addButton(
            $grid, 'Edit...', 'Edit the selected world model object', undef,
            9, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number && $self->session->worldModelObj->ivExists('modelHash', $number)) {

                # Open up an 'edit' window for the child object
                $self->openChildEditWin($number);

                # Refresh the simple lists and reset entry boxes
                $self->family2Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button3 = $self->addButton($grid,
            'Remove child', 'Remove the selected world model object from the list', undef,
            1, 4, 11, 12,
            TRUE);      # Irreversible
        $button3->signal_connect('clicked' => sub {

            my ($number, $childObj);

            ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number && $self->session->worldModelObj->ivExists('modelHash', $number)) {

                # If the edited object is a region, we can't remove a child region or room from
                #   this window. The call to ->removeChild already checks for this, but we can
                #   provide an informative dialogue to explain the problem
                $childObj = $self->session->worldModelObj->ivShow('modelHash', $number);
                if (
                    $self->editObj->category eq 'region'
                    && ($childObj->category eq 'region' || $childObj->category eq 'room')
                ) {
                    $self->showMsgDialogue(
                        'Remove child object',
                        'warning',
                        'A child region or room cannot be removed from a parent region using this'
                        . ' tab',
                        'ok',
                    );

                # Attempt to remove the child
                } elsif (
                    ! $self->session->worldModelObj->removeChild(
                        TRUE,           # Update Automapper windows
                        $self->editObj->number,
                        $number,
                    )
                ) {
                    $self->showMsgDialogue(
                        'Add model object',
                        'error',
                        'Failed to remove the child object',
                        'ok',
                    );

                } else {

                    # Update the simple list
                    $self->family2Tab_refreshList($slWidget, (scalar @columnList / 2));
                }

                # In either case, reset the entry box
                $self->resetEntryBoxes($entry);
            }
        });

        my $button4 = $self->addButton($grid,
            'Delete from model', 'Delete the selected world model object', undef,
            4, 6, 11, 12,
            TRUE);      # Irreversible
        $button4->signal_connect('clicked' => sub {

            my ($number, $obj);

            ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number && $self->session->worldModelObj->ivExists('modelHash', $number)) {

                $obj = $self->session->worldModelObj->ivShow('modelHash', $number);

                # Attempt to delete the child from the world model
                if (
                    ! $self->session->worldModelObj->deleteObj(
                        $self->session,
                        TRUE,           # Update Automapper windows
                        $obj,
                      )
                ) {
                    $self->showMsgDialogue(
                        'Delete model object',
                        'error',
                        'Failed to delete the child object',
                        'ok',
                    );

                } else {

                    # Update the simple list
                    $self->family2Tab_refreshList($slWidget, (scalar @columnList / 2));
                }

                # In either case, reset the entry box
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub family1Tab_refreshList {

        # Called by $self->family1Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $parentNum, $parentObj,
            @dataList,
        );

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

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

        # Import the IV
        $parentNum = $self->getEditHash_scalarIV('parent');
        if ($parentNum) {

            $parentObj = $self->session->worldModelObj->ivShow('modelHash', $parentNum);
        }

        # Compile the simple list data
        if ($parentNum && $parentObj) {

            push (@dataList,
                $parentNum,
                $parentObj->category,
                $parentObj->name,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub family2Tab_refreshList {

        # Called by $self->family2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @sortedList, @dataList,
            %ivHash,
        );

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

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

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('childHash');

        # Get a sorted list of child object numbers
        @sortedList = sort {$a <=> $b} (keys %ivHash);

        # Compile the simple list data
        foreach my $childNum (@sortedList) {

            my $childObj = $self->session->worldModelObj->ivShow('modelHash', $childNum);

            push (@dataList,
                $childObj->number,
                $childObj->category,
                $childObj->name,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub notesTab {

        # Notes tab
        #
        # 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 . '->notesTab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, 'N_otes');

        # Notes
        $self->addLabel($grid, '<b>Notes</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Any additional information about this object you want to retain between'
            . ' sessions</i>',
            1, 12, 1, 2);

        my $textView = $self->addTextView($grid, 'notesList', TRUE,
            1, 12, 2, 10,
            TRUE, FALSE, FALSE, FALSE,  # Treat as list, don't remove empty lines or whitespace
        );
        my $buffer = $textView->get_buffer();

        # Add a button to clear the list
        my $button = $self->addButton($grid,
            'Clear notes', 'Delete your notes for this object', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Reset the IV
            $self->ivAdd('editHash', 'notesList', []);
            # Empty the list displayed in the tab
            $buffer->set_text('');
        });

        # Tab complete
        return 1;
    }

    # Group 2

    sub wordsTab {

        # Words tab
        #
        # 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 . '->wordsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Words');

        # Add tabs to the inner notebook
        $self->words1Tab($innerNotebook);
        $self->words2Tab($innerNotebook);
        $self->words3Tab($innerNotebook);
        $self->words4Tab($innerNotebook);

        return 1;
    }

    sub words1Tab {

        # Words1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Words', 'Inventory task properties'],
        );

        # Left column
        $self->addLabel($grid, '<b>Words</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Main noun',
            1, 3, 1, 2);
        $self->addEntryWithIcon($grid, 'noun', 'string', 1, undef,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Noun tag',
            1, 3, 2, 3);
        $self->addEntry($grid, 'nounTag', FALSE,
            3, 6, 2, 3);

        $self->addLabel($grid, 'Base string',
            1, 3, 3, 4);
        $self->addEntryWithButton($grid, 'baseString', TRUE,
            3, 12, 3, 4);
        $self->addLabel($grid, 'Description',
            1, 3, 4, 5);
        $self->addEntryWithButton($grid, 'descrip', TRUE,
            3, 12, 4, 5);

        # Inventory task properties
        $self->addLabel($grid, '<b>Inventory task properties</b>',
            0, 12, 5, 6);
        $self->addLabel($grid, 'Container model number',
            1, 3, 6, 7);
        $self->addEntry($grid, 'container', FALSE,
            3, 6, 6, 7);
        $self->addLabel($grid, 'Inventory type',
            1, 3, 7, 8);
        $self->addEntry($grid, 'inventoryType', FALSE,
            3, 6, 7, 8);

        # Right column
        $self->addLabel($grid, 'Multiple',
            7, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'multiple', 'float', 0, undef,
            9, 12, 1, 2);

        # Tab complete
        return 1;
    }

    sub words2Tab {

        # Words2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Other nouns', 'Adjectives'],
        );

        # Other nouns
        $self->addLabel($grid, '<b>Other nouns</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>Other words for this object which are known to be nouns (besides the main one)</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'otherNounList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Adjectives
        $self->addLabel($grid, '<b>Adjectives</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Words for this object that are known to be adjectives</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'adjList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub words3Tab {

        # Words3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Pseudo adjectives', 'Root adjectives'],
        );

        # Pseudo adjectives
        $self->addLabel($grid, '<b>Pseudo adjectives</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>Adjectives for this object which are linked to longer terms (e.g. \'big\' for'
            . ' \'big-looking\')</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'pseudoAdjList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Root adjectives
        $self->addLabel($grid, '<b>Root adjectives</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Root forms of declined adjectives (not found in English)</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'rootAdjList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub words4Tab {

        # Words4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Unknown words'],
        );

        # Unknown words
        $self->addLabel($grid, '<b>Unknown words</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>Other words for this object which aren\'t known nouns or adjectives</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'unknownWordList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    # Group 3

    sub attacksTab {

        # Attacks tab
        #
        # 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 . '->attacksTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Attacks',
            ['Properties for use by your code during attacks'],
        );

        # Left column
        $self->addLabel($grid, '<b>Properties for use by your code during attacks</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Target status',
            1, 3, 1, 2);
        $self->addEntry($grid, 'targetStatus', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Target (attack) type',
            1, 3, 2, 3);
        $self->addEntry($grid, 'targetType', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Target path',
            1, 3, 3, 4);
        $self->addEntry($grid, 'targetPath', FALSE,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Target room number',
            1, 3, 4, 5);
        $self->addEntry($grid, 'targetRoomNum', FALSE,
            3, 6, 4, 5);

        # Right column
        # Add empty label for spacing
        $self->addLabel($grid, '',
            6, 7, 1, 2);
        $self->addCheckButton($grid, 'Already attacked', 'alreadyAttackedFlag', FALSE,
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Explicit in description', 'explicitFlag', TRUE,
            7, 12, 2, 3);

        # Tab complete
        return 1;
    }

    # Group 4

    sub physicalTab {

        # Physical tab
        #
        # 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 . '->physicalTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'P_hysical');

        # Add tabs to the inner notebook
        $self->physical1Tab($innerNotebook);
        $self->physical2Tab($innerNotebook);

        return 1;
    }

    sub physical1Tab {

        # Physical1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Physical properties'],
        );

        # Left column
        $self->addLabel($grid, '<b>Physical properties</b>',
            0, 12, 0, 1);
        $self->addCheckButton($grid, 'Explicitly in description', 'explicitFlag', TRUE,
            1, 6, 1, 2);
        $self->addLabel($grid, 'Weight (if known)',
            1, 3, 2, 3);
        $self->addEntryWithButton($grid, 'weight', TRUE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Condition (0-100)',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'condition', 'int', 0, 100,
            3, 6, 3, 4, 4, 4);
        $self->addCheckButton($grid, 'Condition change flag', 'conditionChangeFlag', FALSE,
            1, 6, 4, 5);

        # Right column
        $self->addCheckButton($grid, 'Fixable', 'fixableFlag', TRUE,
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Sellable', 'sellableFlag', TRUE,
            7, 12, 2, 3);
        $self->addLabel($grid, 'Value (when bought)',
            7, 9, 3, 4);
        $self->addEntryWithButton($grid, 'buyValue', TRUE,
            9, 12, 3, 4);
        $self->addLabel($grid, 'Value (when sold)',
            7, 9, 4, 5);
        $self->addEntryWithButton($grid, 'sellValue', TRUE,
            9, 12, 4, 5);

        # Tab complete
        return 1;
    }

    sub physical2Tab {

        # Physical2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Bonus stats'],
        );

        # Bonus stats
        $self->addLabel($grid, '<b>Bonus stats</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Stat bonuses and penalties when using this object</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Stat', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'bonusHash');

        # Add entry boxes and edit buttons
        $self->addLabel($grid, 'Stat',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Bonus/penalty',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', undef, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'bonusHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($unit, $value);

            $unit = $entry->get_text();
            $value = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('bonusHash', $unit, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'bonusHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub exclusiveTab {

        # Exclusive tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @sortedList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Exclusive',
            ['Exclusive profiles'],
        );

        # Exclusive profiles
        $self->addLabel($grid, '<b>Exclusive profiles</b>',
            0, 6, 0, 1);
        $self->addCheckButton(
            $grid,
            'Only exclusive profiles (characters, races, guilds, etc) can use this object?',
            'exclusiveFlag',
            TRUE,
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Exclusive profile', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));

        # Prepare a combo box with all of the current session's profiles (i.e. excluding all worlds
        #   except the current one)
        @sortedList = sort {lc($a) cmp lc($b)} ($self->session->ivKeys('profHash'));
        # Add the combo box
        $self->addLabel($grid, 'Exclusive profile:',
            1, 3, 10, 11);
        my $comboBox = $self->addComboBox($grid, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 10, 11);

        # Add buttons
        my $button = $self->addButton($grid,
            'Exclusive', 'Make the chosen profile exclusive', undef,
            6, 8, 10, 11);
        $button->signal_connect('clicked' => sub {

            my $profName = $comboBox->get_active_text();
            if ($profName && $self->session->ivExists('profHash', $profName)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV(
                    'exclusiveHash',
                    $profName,
                    $self->session->ivShow('profHash', $profName),
                );

                # Refresh the simple list
                $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button2 = $self->addButton($grid,
            'Not exclusive', 'Make the selected profile un-exclusive', undef,
            8, 10, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my ($profName) = $self->getSimpleListData($slWidget, 0);
            if (defined $profName) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('exclusiveHash', $profName, undef, TRUE);

                # Refresh the simple list
                $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button3 = $self->addButton($grid,
            'Clear', 'Clear the list of exclusive profiles', undef,
            10, 12, 10, 11);
        $button3->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'exclusiveHash', {});

            # Refresh the simple list
            $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub exclusiveTab_refreshList {

        # Resets the simple list displayed by $self->exclusiveTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

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

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

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('exclusiveHash');

        # Compile the simple list data
        @dataList = sort {lc($a) cmp lc($b)} (keys %ivHash);

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    # Group 5

    sub sentient1Tab {

        # Sentient1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@comboList, @comboList2);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Properties of ' . $self->editObj->category . ' model objects (1/2)'],
        );

        # Left column
        $self->addLabel($grid,
            '<b>Properties of ' . $self->editObj->category . ' model objects (1/2)</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Guild',
            1, 3, 1, 2);
        @comboList = sort {lc($a) cmp lc($b)} ($self->session->currentDict->ivKeys('guildHash'));
        $self->addComboBox($grid, 'guild', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 1, 2);

        $self->addLabel($grid, 'Race',
            1, 3, 2, 3);
        @comboList = sort {lc($a) cmp lc($b)} ($self->session->currentDict->ivKeys('raceHash'));
        $self->addComboBox($grid, 'race', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);

        # Right column
        $self->addLabel($grid, 'Level (if known)',
            7, 10, 1, 2);
        $self->addEntryWithIcon($grid, 'level', 'int', 0, undef,
            10, 12, 1, 2, 8, 8);
        $self->addLabel($grid, '(not less than)',
            8, 10, 2, 3);
        $self->addEntryWithIcon($grid, 'weakerLevel', 'int', 0, undef,
            10, 12, 2, 3, 8, 8);
        $self->addLabel($grid, '(not more than)',
            8, 10, 3, 4);
        $self->addEntryWithIcon($grid, 'strongerLevel', 'int', 0, undef,
            10, 12, 3, 4, 8, 8);

        # Tab complete
        return 1;
    }

    sub sentient2Tab {

        # Sentient2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Inventory list'],
        );

        # Inventory list
        $self->addLabel($grid, '<b>Inventory list</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of things the ' . $self->editObj->category . ' typically carries in its'
            . ' inventory</i>',
            1, 12, 1, 2);

        $self->addTextView($grid, 'inventoryList', TRUE,
            1, 12, 2, 12,
            TRUE, TRUE, TRUE, FALSE,    # Treat as list, remove empty lines, do remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub sentient3Tab {

        # Sentient3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Cash list'],
        );

        # Cash list
        $self->addLabel($grid, '<b>Cash list</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Amounts of cash found on the ' . $self->editObj->category . '\'s corpse (most'
            . ' recent ten amounts)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Amount' => 'text',
        );

        my $slWidget = $self->addSimpleList($grid, 'cashList', \@columnList,
            1, 12, 2, 10);

        # Add a button
        my $button = $self->addButton($grid, 'Clear list', 'Clear the list of cash amounts', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Add an empty list to $self->editHash
            $self->ivAdd('editHash', 'cashList', []);

            # Refresh the simple list
            $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'cashList');
        });

        # Tab complete
        return 1;
    }

    sub sentient4Tab {

        # Sentient4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@comboList, @comboList2);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Properties of ' . $self->editObj->category . ' model objects (2/2)'],
        );

        # Left column
        $self->addLabel($grid,
            '<b>Properties of ' . $self->editObj->category . ' model objects (2/2)</b>',
            0, 12, 0, 1);

        $self->addCheckButton($grid, 'Ever initiated combat (unfriendly)', 'unfriendlyFlag', TRUE,
            1, 6, 1, 2);

        $self->addLabel($grid, 'Morality',
            1, 5, 2, 3);
        @comboList = ('good', 'neutral', 'evil');
        $self->addComboBox($grid, 'morality', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 2, 3);

        $self->addLabel($grid, 'Attached to quest',
            1, 3, 3, 4);
        @comboList2 = $self->session->currentWorld->ivKeys('questHash');
        $self->addComboBox($grid, 'questName', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            3, 6, 3, 4);

        # Right column
        $self->addCheckButton($grid, 'Tends to wander around', 'wanderFlag', TRUE,
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Ever fleed combat', 'fleeFlag', TRUE,
            7, 12, 2, 3);
        $self->addCheckButton($grid, 'Tends to flee combat quickly', 'quickFleeFlag', TRUE,
            7, 12, 3, 4);
        $self->addCheckButton($grid, 'Should NEVER be attacked', 'noAttackFlag', TRUE,
            7, 12, 4, 5);
        $self->addCheckButton($grid, 'Mercies opponents (doesn\'t kill)', 'mercyFlag', TRUE,
            7, 12, 5, 6);

        # Tab complete
        return 1;
    }

    sub sentient5Tab {

        # Sentient5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Action properties'],
        );

        # Action properties
        $self->addLabel($grid, '<b>Action properties</b>',
            0, 12, 0, 1);
        $self->addCheckButton(
            $grid,
            'Flag to be set if the ' . $self->editObj->category . ' has ever performed an action',
            'actionFlag',
            TRUE,
            1, 12, 1, 2);

        $self->addLabel($grid,
            '<i>List of strings received when the ' . $self->editObj->category
            . ' performs an action:</i>',
            1, 10, 2, 3);
        $self->addTextView($grid, 'actionList', TRUE,
            1, 12, 3, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub sentient6Tab {

        # Sentient6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Conversation properties'],
        );

        # Conversation properties
        $self->addLabel($grid, '<b>Conversation properties</b>',
            0, 12, 0, 1);

        $self->addCheckButton(
            $grid,
            'Flag to be set if the ' . $self->editObj->category . ' has ever said anything',
            'talkativeFlag',
            TRUE,
            1, 12, 1, 2);

        $self->addLabel($grid,
            '<i>List of things spoken by the ' . $self->editObj->category . ':</i>',
            1, 10, 2, 3);
        $self->addTextView($grid, 'talkList', TRUE,
            1, 12, 3, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub character1Tab {

        # Character1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@comboList, @comboList2, @comboList3, @comboList4);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Properties of ' . $self->editObj->category . ' model objects'],
        );

        # Left column
        $self->addLabel($grid, '<b>Properties of ' . $self->editObj->category
            . ' model objects</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Guild',
            1, 3, 1, 2);
        @comboList = $self->session->currentDict->ivKeys('guildHash');
        $self->addComboBox($grid, 'guild', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 1, 2);

        $self->addLabel($grid, 'Race',
            1, 3, 2, 3);
        @comboList2 = $self->session->currentDict->ivKeys('raceHash');
        $self->addComboBox($grid, 'race', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);

        if ($self->editObj->category eq 'char') {

            # Characters
            $self->addCheckButton($grid, 'Own character', 'ownCharFlag', TRUE,
                1, 6, 3, 4);
            $self->addLabel($grid, 'Owner',
                1, 3, 4, 5);
            $self->addEntryWithButton($grid, 'owner', TRUE,
                3, 6, 4, 5);

            $self->addLabel($grid, 'Mortal status',
                1, 3, 5, 6);
            @comboList3 = ('mortal', 'wiz', 'test');
            $self->addComboBox($grid, 'mortalStatus', \@comboList3, '',
                TRUE,               # No 'undef' value used
                3, 6, 5, 6);

            $self->addLabel($grid, 'Diplomatic status',
                1, 3, 6, 7);
            @comboList4 = ('friendly', 'neutral', 'hostile');
            $self->addComboBox($grid, 'diplomaticStatus', \@comboList4, '',
                TRUE,               # No 'undef' value used
                3, 6, 6, 7);

            $self->addCheckButton($grid, 'Ever attacked you', 'grudgeFlag', TRUE,
                1, 6, 7, 8);
            $self->addCheckButton(
                $grid, 'Fight back if attacked by this character', 'fightBackFlag', TRUE,
                1, 6, 8, 9);

        } elsif ($self->editObj->category eq 'minion') {

            # Minions
            $self->addLabel($grid, 'Minion cost',
                1, 3, 3, 4);
            $self->addEntryWithIcon($grid, 'value', 'string', 0, undef,
                3, 6, 3, 4);
            $self->addCheckButton($grid, 'Own minion', 'ownMinionFlag', TRUE,
                1, 6, 4, 5);
        }

        # Right column
        $self->addLabel($grid, 'Level (if known)',
            7, 10, 1, 2);
        $self->addEntryWithIcon($grid, 'level', 'int', 0, undef,
            10, 12, 1, 2, 8, 8);
        $self->addLabel($grid, '(not less than)',
            7, 10, 2, 3);
        $self->addEntryWithIcon($grid, 'weakerLevel', 'int', 0, undef,
            10, 12, 2, 3, 8, 8);
        $self->addLabel($grid, '(not more than)',
            7, 10, 3, 4);
        $self->addEntryWithIcon($grid, 'strongerLevel', 'int', 0, undef,
            10, 12, 3, 4, 8, 8);

        if ($self->editObj->category eq 'char') {

            $self->addLabel($grid, 'Total XP (if known)',
                7, 10, 4, 5);
            $self->addEntryWithButton($grid, 'totalXP', 1,
                10, 12, 4, 5);
            $self->addLabel($grid, 'Total QP (if known)',
                7, 10, 5, 6);
            $self->addEntryWithButton($grid, 'totalQP', 1,
                10, 12, 5, 6);
        }

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Region;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Region')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 5
        $self->regionTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    sub regionTab {

        # Region tab
        #
        # 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 . '->regionTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Region',
            ['General properties'],
        );

        # General properties
        $self->addLabel($grid, '<b>General properties</b>',
            0, 6, 0, 1);

        # Temporary/finished region
        $self->addCheckButton($grid, 'Temporary region', 'tempRegionFlag', FALSE,
            1, 12, 1, 2);
        $self->addCheckButton($grid, 'Finished region', 'finishedFlag', FALSE,
            1, 12, 2, 3);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Room;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Room')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 5
        $self->roomTab();
        $self->exitsTab();
        $self->contentsTab();
        $self->protocolsTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my ($wmObj, $hashRef);

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

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

        # Import the world model (for convenience)
        $wmObj = $self->session->worldModelObj;

        if ($self->editHash) {

            # For changes to ->involuntaryExitPatternHash, other objects in the world model must be
            #   updated
            if ($self->ivExists('editHash', 'involuntaryExitPatternHash')) {

                # Simplest way to deal with changes to involuntary exits is to delete all the
                #   existing patterns (if any), then add new ones (if any)
                foreach my $pattern ($self->editObj->ivKeys('involuntaryExitPatternHash')) {

                    $wmObj->removeInvoluntaryExit($self->editObj, $pattern);
                }

                $hashRef = $self->ivShow('editHash', 'involuntaryExitPatternHash');
                foreach my $pattern (keys %$hashRef) {

                    $wmObj->addInvoluntaryExit($self->editObj, $pattern, $$hashRef{$pattern});
                }

                $self->ivDelete('editHash', 'involuntaryExitPatternHash');
            }

            # Same process for ->repulseExitPatternHash
            if ($self->ivExists('editHash', 'repulseExitPatternHash')) {

                # Simplest way to deal with changes to repulse exits is to delete all the existing
                #   patterns (if any), then add new ones (if any)
                foreach my $pattern ($self->editObj->ivKeys('repulseExitPatternHash')) {

                    $wmObj->removeRepulseExit($self->editObj, $pattern);
                }

                $hashRef = $self->ivShow('editHash', 'repulseExitPatternHash');
                foreach my $pattern (keys %$hashRef) {

                    $wmObj->addRepulseExit($self->editObj, $pattern, $$hashRef{$pattern});
                }

                $self->ivDelete('editHash', 'repulseExitPatternHash');
            }

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Redraw the room in every automapper window using the same world model
            $wmObj->updateMaps('room', $self->editObj);
        }

        return 1;
    }

    # Group 5

    sub roomTab {

        # Room tab
        #
        # 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 . '->roomTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Room');

        # Add tabs to the inner notebook
        $self->room1Tab($innerNotebook);
        $self->room2Tab($innerNotebook);
        $self->room3Tab($innerNotebook);
        $self->room4Tab($innerNotebook);
        $self->room5Tab($innerNotebook);
        $self->room6Tab($innerNotebook);
        $self->room7Tab($innerNotebook);

        return 1;
    }

    sub room1Tab {

        # Room1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [
                'Properties of ' . $self->editObj->category . ' model objects',
                'Wilderness mode',
                'Regionmap grid position',
                $axmud::BASIC_NAME . ' list',
            ],
        );

        # Left column
        $self->addLabel($grid,
            '<b>Properties of ' . $self->editObj->category . ' model objects</b>',
            0, 6, 0, 1);

        $self->addLabel($grid, 'Room tag',
            1, 2, 1, 2);
        $self->addEntry($grid, 'roomTag', FALSE,
            2, 6, 1, 2);
        $self->addLabel($grid, 'Offset on map',
            1, 2, 2, 3);
        $self->addLabel($grid, 'X:',
            2, 3, 2, 3);
        $self->addEntry($grid, 'roomTagXOffset', FALSE,
            3, 4, 2, 3, 6, 6);
        $self->addLabel($grid, 'Y:',
            4, 5, 2, 3);
        $self->addEntry($grid, 'roomTagYOffset', FALSE,
            5, 6, 2, 3, 6, 6);

        $self->addLabel($grid, 'Room guild',
            1, 2, 3, 4);
        $self->addEntry($grid, 'roomGuild', FALSE,
            2, 6, 3, 4);
        $self->addLabel($grid, 'Offset on map',
            1, 2, 4, 5);
        $self->addLabel($grid, 'X:',
            2, 3, 4, 5);
        $self->addEntry($grid, 'roomGuildXOffset', FALSE,
            3, 4, 4, 5, 6, 6);
        $self->addLabel($grid, 'Y:',
            4, 5, 4, 5);
        $self->addEntry($grid, 'roomGuildYOffset', FALSE,
            5, 6, 4, 5, 6, 6);

        $self->addCheckButton($grid, 'Unspecified room description', 'unspecifiedFlag', FALSE,
            1, 6, 5, 6);
        $self->addCheckButton($grid, 'Currently dark', 'currentlyDarkFlag', FALSE,
            1, 6, 6, 7);

        # Wilderness mode
        $self->addLabel($grid, '<b>Wilderness mode</b>',
            0, 6, 7, 8);
        my $entry = $self->addEntry($grid, undef, FALSE,
            1, 6, 8, 9);
        if ($self->editObj->wildMode eq 'normal') {
            $entry->set_text('\'normal\' - exits are required between rooms');
        } elsif ($self->editObj->wildMode eq 'border') {
            $entry->set_text('\'border\' - assume exits exist between adjacent wild rooms');
        } elsif ($self->editObj->wildMode eq 'wild') {
            $entry->set_text('\'wild\' - assume exits exist between all adjacent rooms');
        }

        # Empty labels for spacing
        $self->addLabel($grid, '',
            1, 6, 9, 10);
        $self->addLabel($grid, '',
            1, 6, 10, 11);
        $self->addLabel($grid, '',
            1, 6, 11, 12);

        # Right column
        $self->addLabel($grid, '<b>Regionmap grid position</b>',
            7, 13, 0, 1);
        $self->addLabel($grid, 'X block',
            8, 11, 1, 2);
        $self->addEntry($grid, 'xPosBlocks', FALSE,
            11, 13, 1, 2, 4, 4);
        $self->addLabel($grid, 'Y block',
            8, 11, 2, 3);
        $self->addEntry($grid, 'yPosBlocks', FALSE,
            11, 13, 2, 3, 4, 4);
        $self->addLabel($grid, 'Z block',
            8, 11, 3, 4);
        $self->addEntry($grid, 'zPosBlocks', FALSE,
            11, 13, 3, 4, 4, 4);

        # Axbasic list
        $self->addLabel($grid, '<b>' . $axmud::BASIC_NAME . ' list</b>',
            7, 13, 4, 5);
        $self->addLabel(
            $grid,
            '<i>List of ' . $axmud::BASIC_NAME . ' scripts run when the room is entered</i>',
            8, 13, 5, 6);
        $self->addTextView($grid, 'arriveScriptList', TRUE,
            8, 13, 6, 12,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub room2Tab {

        # Room2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Optional arguments
        #   $tabTitle   - When inherited by GA::EditWin::Painter, the tab's page number (a string)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $wmObj, $roomFlag, $roomFlagObj, $colour, $noUpdateFlag,
            @columnList, @comboList, @comboList2,
            %showHash,
        );

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

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

        # Import the world model (for convenience)
        $wmObj = $self->session->worldModelObj;
        # Work out which room flags should be visible, and which hidden, depending on the current
        #   value of GA::Obj::WorldModel->roomFlagShowMode. Those which should be shown are added to
        #   this hash
        %showHash = $wmObj->getVisibleRoomFlags();
        # Add to this hash any room flags which are already used by the room
        foreach my $roomFlag ($self->editObj->ivKeys('roomFlagHash')) {

            $showHash{$roomFlag} = $wmObj->ivShow('roomFlagHash', $roomFlag);
        }

        # Tab setup
        if (! $tabTitle) {

            $tabTitle = 'Page _2';      # This function wasn't called by GA::EditWin::Painter
        }

        my $grid = $self->addTab(
            $innerNotebook,
            $tabTitle,
            ['Room flags'],
        );

        # Room flags
        $self->addLabel($grid, '<b>Room flags</b>',
            0, 6, 0, 1);
        $self->addLabel($grid,
            '<i>Flags that determine how the room is drawn in the automapper</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Room flag', 'text',
            'Description', 'text',
            'Priority', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->room2Tab_refreshList($slWidget, scalar (@columnList / 2), \%showHash);

        # Add editing widgets
        $self->addLabel($grid, 'Room flag category (filter)',
            1, 4, 10, 11);
        $self->addLabel($grid, 'Room flag',
            6, 7, 10, 11);

        my $combo;
        if ($self->session->worldModelObj->roomFlagShowMode eq 'default') {

            @comboList = $axmud::CLIENT->constRoomFilterList;
            $combo = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,              # 'undef' value not used
                4, 6, 10, 11);
            # ->signal_connect appears below

            @comboList2
                = $self->session->worldModelObj->getRoomFlagsInFilter($combo->get_active_text());

        } else {

            @comboList = ('n/a');
            $combo = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,              # 'undef' value not used
                4, 6, 10, 11);
            $combo->set_sensitive(FALSE);
            # ->signal_connect appears below

            foreach my $roomFlagObj (sort {$a->priority <=> $b->priority} (values %showHash)) {

                push (@comboList2, $roomFlagObj->name);
            }
        }

        my $combo2 = $self->addComboBox($grid, undef, \@comboList2, '',
            TRUE,              # 'undef' value not used
            7, 10, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($grid, 'Colour',
            10, 11, 10, 11);
        $roomFlag = $combo2->get_active_text();
        if ($roomFlag) {

            $roomFlagObj = $self->session->worldModelObj->ivShow('roomFlagHash', $roomFlag);
            if ($roomFlagObj) {

                $colour = $roomFlagObj->colour;
            }
        }
        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($grid, $colour, undef,
            11, 12, 10, 11);

        # ->signal_connect from above
        $combo->signal_connect('changed' => sub {

            my ($text, $text2);

            $text = $combo->get_active_text();

            # Don't let the ->signal_connect below react before we're ready
            $noUpdateFlag = TRUE;
            $self->resetComboBox(
                $combo2,
                $self->session->worldModelObj->getRoomFlagsInFilter($text),
            );

            $noUpdateFlag = FALSE;

            $text2 = $combo2->get_active_text();
            if ($text2) {
                $roomFlagObj = $self->session->worldModelObj->ivShow('roomFlagHash', $text2);
            } else {
                $roomFlagObj = undef;
            }

            if ($roomFlagObj) {
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $roomFlagObj->colour);
            } else {
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
            }
        });

        $combo2->signal_connect('changed' => sub {

            my $text2 = $combo2->get_active_text();

            if (! $noUpdateFlag) {

                if ($text2) {
                    $roomFlagObj = $self->session->worldModelObj->ivShow('roomFlagHash', $text2);
                } else {
                    $roomFlagObj = undef;
                }

                if ($roomFlagObj) {
                    $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $roomFlagObj->colour);
                } else {
                    $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
                }
            }
        });

        $self->addLabel($grid, 'Last flag drawn',
            1, 3, 11, 12);
        $self->addEntry($grid, 'lastRoomFlag', FALSE,
            3, 6, 11, 12);

        my $button = $self->addButton($grid,
            'Use', 'The automapper can use this room flag when drawing the room', undef,
            6, 8, 11, 12);
        $button->signal_connect('clicked' => sub {

            my $flag = $combo2->get_active_text();
            if ($flag) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('roomFlagHash', $flag, undef);

                # Refresh the simple list
                $self->room2Tab_refreshList($slWidget, scalar (@columnList / 2), \%showHash);
            }
        });

        my $button2 = $self->addButton($grid,
            'Don\'t use', 'The automapper can\'t use this room flag when drawing the room', undef,
            8, 10, 11, 12);
        $button2->signal_connect('clicked' => sub {

            my $flag = $combo2->get_active_text();
            if ($flag) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('roomFlagHash', $flag, undef, TRUE);

                # Refresh the simple list
                $self->room2Tab_refreshList($slWidget, scalar (@columnList / 2), \%showHash);
            }
        });

        my $button3 = $self->addButton($grid, 'Clear', 'Clear the list of room flags', undef,
            10, 12, 11, 12);
        $button3->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'roomFlagHash', {});

            # Refresh the simple list
            $self->room2Tab_refreshList($slWidget, scalar (@columnList / 2), \%showHash);
        });

        # Tab complete
        return 1;
    }

    sub room2Tab_refreshList {

        # Called by $self->room2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $hashRef    - Reference to a hash of room flags that should be visible, depending on the
        #                   current value of GA::Obj::WorldModel->roomFlagShowMode, and including
        #                   any room flags already used by this room (might be an empty hash). Hash
        #                   in the form
        #                       $$hashRef{room_flag_name} = room_flag_object
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $hashRef, $check) = @_;

        # Local variables
        my (
            $wmObj,
            @list, @sortedList, @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $hashRef|| defined $check) {

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

        # Import the world model (for speed)
        $wmObj = $self->session->worldModelObj;
        # Import the IV, and sort by priority
        %ivHash = $self->getEditHash_hashIV('roomFlagHash');
        foreach my $key (keys %ivHash) {

            my $roomFlagObj = $wmObj->ivShow('roomFlagHash', $key);
            if ($roomFlagObj) {

                push (@list, $roomFlagObj);
            }
        }

        @sortedList = sort {$a->priority <=> $b->priority} (@list);

        # Compile the simple list data
        foreach my $roomFlagObj (@sortedList) {

            if (exists $$hashRef{$roomFlagObj->name}) {

                push (@dataList,
                    $roomFlagObj->name,
                    $roomFlagObj->descrip,
                    $roomFlagObj->priority,
                );
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub room3Tab {

        # Room3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Room titles', 'Room commands', 'Unspecified room patterns'],
        );

        # Room titles
        $self->addLabel($grid, '<b>Room titles</b>',
            0, 6, 0, 2);
        $self->addLabel($grid, '<i>List of room titles (brief descriptions) for this room</i>',
            1, 6, 2, 4);
        $self->addTextView($grid, 'titleList', TRUE,
            1, 6, 4, 6);

        # Room commands
        $self->addLabel($grid, '<b>Room commands</b>',
            6, 12, 0, 2);
        $self->addLabel($grid, '<i>List of commands available in this room</i>',
            7, 12, 2, 4);
        $self->addTextView($grid, 'roomCmdList', TRUE,
            7, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Unspecified room patterns
        $self->addLabel($grid, '<b>Unspecified room patterns</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $grid,
            '<i>Patterns seen when the character moves to a room without a recognisable room'
            . ' statement</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'unspecifiedPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub room4Tab {

        # Room4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Optional arguments
        #   $tabTitle       - When inherited by GA::EditWin::Painter, the tab's page number (a
        #                       string)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @comboList);

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

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

        # Tab setup
        if (! $tabTitle) {

            $tabTitle = 'Page _4';  # This function wasn't called by GA::EditWin::Painter
        }

        my $grid = $self->addTab(
            $innerNotebook,
            $tabTitle ,
            ['Verbose descriptions'],
        );

        # Verbose descriptions
        $self->addLabel($grid, '<b>Verbose descriptions</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of verbose descriptions for each light status</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Light status', 'text',
            'Verbose descrip', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'descripHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Light status',
            1, 3, 8, 9);
        @comboList = $self->session->worldModelObj->lightStatusList;
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($grid, 'Verbose description',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'descripHash',
            10,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($status, $descrip);

            $status = $combo->get_active_text();
            $descrip = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('descripHash', $status, $descrip);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'descripHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub room5Tab {

        # Room5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Optional arguments
        #   $tabTitle       - When inherited by painter object (a non-model GA::ModelObj::Room),
        #                       the tab's page number (a string)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        if (! $tabTitle) {

            $tabTitle = 'Page _5';  # This function wasn't called by the painter object
        }

        my $grid = $self->addTab(
            $innerNotebook,
            $tabTitle,
            ['Noun list', 'Adjective list'],
        );

        # Noun list
        $self->addLabel($grid, '<b>Noun list</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>List of recognised nouns that appear in this room and its contents</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'nounList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Adjective list
        $self->addLabel($grid, '<b>Adjective list</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>List of recognised adjectives that appear in this room and its contents</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'adjList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub room6Tab {

        # Room6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @charList, @sortedList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Character visits'],
        );

        # Character visits
        $self->addLabel($grid, '<b>Character visits</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Record of how many visits each character has made to this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Character', 'text',
            'Number of visits', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'visitHash');

        # Get a sorted list of characters, not including the current character (if any)
        foreach my $profObj ($self->session->ivValues('profHash')) {

            if (
                $profObj->category eq 'char'
                && (
                    ! $self->session->currentChar
                    || $self->session->currentChar ne $profObj
                )
            ) {
                push (@charList, $profObj->name);
            }
        }

        @sortedList = sort {lc($a) cmp lc($b)} (@charList);

        # Put the current char (if any) at the beginning of the list
        if ($self->session->currentChar) {

            unshift (@sortedList, $self->session->currentChar->name);
        }

        # Add entries/comboboxes for adding new key/value pairs
        $self->addLabel($grid, 'Character',
            1, 3, 8, 9);
        my $combo = $self->addComboBox($grid, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($grid, 'Number of visits',
            7, 9, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'visitHash',
            10,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($char, $visitCount);

            $char = $combo->get_active_text();
            $visitCount = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('visitHash', $char, $visitCount);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'visitHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub room7Tab {

        # Room7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Optional arguments
        #   $tabTitle       - When inherited by GA::EditWin::Painter, the tab's page number (a
        #                       string)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @profList, @sortedList);

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

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

        # Tab setup
        if (! $tabTitle) {

            $tabTitle = '_Page _7';     # This function wasn't called by GA::EditWin::Painter
        }

        my $grid = $self->addTab(
            $innerNotebook,
            $tabTitle,
            ['Exclusive profiles'],
        );

        # Exclusive profiles
        $self->addLabel($grid, '<b>Exclusive profiles</b>',
            0, 6, 0, 1);
        $self->addCheckButton(
            $grid,
            'Only exclusive profiles (characters, races, guilds, etc) can enter this room?',
            'exclusiveFlag',
            TRUE,
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Exclusive profile', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list (the function is inherited from GA::EditWin::Generic::ModelObj)
        $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));

        # Prepare a combo box with all of the current session's profiles (excluding all worlds
        #   including the current one)
        foreach my $profObj ($self->session->ivValues('profHash')) {

            if ($profObj->category ne 'world') {

                push (@profList, $profObj->name);
            }
        }

        @sortedList = sort {lc($a) cmp lc($b)} (@profList);

        # Add the combo box
        $self->addLabel($grid, 'Exclusive profile:',
            1, 3, 10, 11);
        my $comboBox = $self->addComboBox($grid, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 10, 11);

        # Add buttons
        my $button = $self->addButton($grid,
            'Exclusive', 'Make the chosen profile exclusive', undef,
            6, 8, 10, 11);
        $button->signal_connect('clicked' => sub {

            my $profName = $comboBox->get_active_text();
            if ($profName && $self->session->ivExists('profHash', $profName)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV(
                    'exclusiveHash',
                    $profName,
                    $self->session->ivShow('profHash', $profName),
                );

                # Refresh the simple list
                $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button2 = $self->addButton($grid,
            'Not exclusive', 'Make the selected profile un-exclusive', undef,
            8, 10, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my ($profName) = $self->getSimpleListData($slWidget, 0);
            if (defined $profName) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('exclusiveHash', $profName, undef, TRUE);

                # Refresh the simple list
                $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button3 = $self->addButton($grid,
            'Clear', 'Clear the list of exclusive profiles', undef,
            10, 12, 10, 11);
        $button3->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'exclusiveHash', {});

            # Refresh the simple list
            $self->exclusiveTab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub exitsTab {

        # Exits tab
        #
        # 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 . '->exitsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Exits');

        # Add tabs to the inner notebook
        $self->exits1Tab($innerNotebook);
        $self->exits2Tab($innerNotebook);
        $self->exits3Tab($innerNotebook);
        $self->exits4Tab($innerNotebook);
        $self->exits5Tab($innerNotebook);
        $self->exits6Tab($innerNotebook);
        $self->exits7Tab($innerNotebook);
        $self->exits8Tab($innerNotebook);
        $self->exits9Tab($innerNotebook);

        return 1;
    }

    sub exits1Tab {

        # Exits1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Failed exit patterns', 'Special departure patterns'],
        );

        # Failed exit patterns
        $self->addLabel($grid, '<b>Failed exit patterns</b>',
            0, 10, 0, 2);
        $self->addLabel(
            $grid,
            '<i>Patterns which mean an exit in this room is (temporarily) unavailable</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'failExitPatternList',
                'list', 'specialDepartPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'failExitPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Special departure patterns
        $self->addLabel($grid, '<b>Special departure patterns</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $grid,
            '<i>Patterns which mean we\'ve left the room (when a new room statement isn\'t'
            . ' sent)</i>',
            1, 12, 8, 10);

        $self->addTextView($grid, 'specialDepartPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub exits2Tab {

        # Exits2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Involuntary exit patterns'],
        );

        # Involuntary exit patterns
        $self->addLabel($grid, '<b>Involuntary exit patterns</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $grid,
            '<i>Patterns which mean the character has left the room involuntarily</i>',
            1, 10, 2, 4);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Optional direction or destination room #', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV(
            $slWidget,
            scalar (@columnList / 2),
            'involuntaryExitPatternHash',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($grid, '(Optional) direction or destination room number',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'involuntaryExitPatternHash',
            10,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $value);

            $pattern = $entry->get_text();
            $value = $entry2->get_text();
            # Use 'undef' rather than an empty string
            if ($value eq '') {

                $value = undef;
            }

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('involuntaryExitPatternHash', $pattern, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'involuntaryExitPatternHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub exits3Tab {

        # Exits3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Repulse exit patterns'],
        );

        # Repulse exit patterns
        $self->addLabel($grid, '<b>Repulse exit patterns</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $grid,
            '<i>Patterns which mean the character has left the room involuntarily after a failed'
            . ' move</i>',
            1, 10, 2, 4);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Optional direction or destination room #', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV(
            $slWidget,
            scalar (@columnList / 2),
            'repulseExitPatternHash',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($grid, '(Optional) direction or destination room number',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'repulseExitPatternHash',
            10,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $value);

            $pattern = $entry->get_text();
            $value = $entry2->get_text();
            # Use 'undef' rather than an empty string
            if ($value eq '') {

                $value = undef;
            }

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('repulseExitPatternHash', $pattern, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'repulseExitPatternHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub exits4Tab {

        # Exits4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Exit objects'],
        );

        # Exit objects
        $self->addLabel($grid, '<b>Exit objects</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of exit objects for each exit in this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Exit #', 'text',
            'Direction', 'text',
            'Map direction', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->exits4Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add entry box for adding new exit objects
        $self->addLabel($grid, 'Direction',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 10, 11);

        # Add editing buttons
        my $button = $self->addButton(
            $grid,
            'Add',
            'Add the direction to the exit model',
            undef,
            6, 8, 10, 11,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my $dir = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                # If the button is sensitised, this must be a world model room, so add the direction
                #   to the exit model
                $self->session->pseudoCmd(
                    'addexit ' . $dir . ' ' . $self->editObj->number,
                    $self->pseudoCmdMode,
                );

                # Refresh the simple list
                $self->exits4Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Edit...',
            'Edit the selected exit object',
            undef,
            8, 10, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

                # Open an 'edit' window for the exit object
                $self->createFreeWin(
                    'Games::Axmud::EditWin::Exit',
                    $self,
                    $self->session,
                    'Edit exit model object #' . $exitObj->number,
                    $exitObj,
                    FALSE,                          # Not temporary
                );

                # Refresh the simple list and reset entry boxes
                $self->exits4Tab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Delete',
            'Delete the selected exit object',
            undef,
            10, 12, 10, 11,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

                # If the button is sensitised, this must be a world model room, so delete the exit
                #   from the exit model
                $self->session->pseudoCmd(
                    'deleteexit ' . $exitNum,
                    $self->pseudoCmdMode,
                );

                # Refresh the simple list and reset entry boxes
                $self->exits4Tab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button4 = $self->addButton(
            $grid,
            'Delete exit and twin',
            'Delete the selected exit object and its twin, if any',
            undef,
            6, 9, 11, 12,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

                # If the button is sensitised, this must be a world model room, so delete the exit
                #   and its twin from the exit model
                $self->session->pseudoCmd(
                    'deleteexit -t ' . $exitNum,
                    $self->pseudoCmdMode,
                );

                # Refresh the simple list and reset entry boxes
                $self->exits4Tab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button5 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of exits',
            undef,
            9, 12, 11, 12,
            TRUE,       # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            # Refresh the simple list and reset entry boxes
            $self->exits4Tab_refreshList($slWidget, scalar (@columnList / 2));
            $self->resetEntryBoxes($entry);
        });

        # If it's a temporary room (not in the world model), desensitise the entry box and buttons
        if (! $self->editObj->modelFlag) {

            $entry->set_state('insensitive');
            $button->set_state('insensitive');
            $button2->set_state('insensitive');
            $button3->set_state('insensitive');
            $button4->set_state('insensitive');
        }

        # Tab complete
        return 1;
    }

    sub exits4Tab_refreshList {

        # Called by $self->exits3Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @sortedExitList, @dataList,
            %exitNumHash,
        );

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

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

        # Import the IVs directly from the edited object (because changes are made directly, and
        #   not stored in $self->editHash)
        @sortedExitList = $self->editObj->sortedExitList;
        %exitNumHash = $self->editObj->exitNumHash;

        # Compile the simple list data
        foreach my $dir (@sortedExitList) {

            my ($number, $exitObj, $mapDir);

            $number = $exitNumHash{$dir};
            if (defined $number) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $number);
            }

            if (defined $exitObj) {

                if ($exitObj->mapDir) {
                    $mapDir = $exitObj->mapDir;
                } else {
                    $mapDir = 'unallocatable';
                }
            }

            if (defined $exitObj && $exitObj->modelFlag) {

                # In exit model
                push (@dataList,
                    $number,
                    $exitObj->dir,
                    $mapDir,
                );

            } else {

                # Not in exit model
                push (@dataList,
                    undef,
                    $dir,
                    undef,
                );
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub exits5Tab {

        # Exits5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Incoming uncertain exits'],
        );

        # Incoming uncertain exits
        $self->addLabel($grid, '<b>Incoming uncertain exits</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of uncertain exits belonging to other rooms which point to this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Room #', 'text',
            'Region', 'text',
            'Exit #', 'text',
            'Direction', 'text',
            'Map direction', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->exits5Tab_refreshList($slWidget, scalar (@columnList / 2), 'uncertainExitHash');

        # Add a single button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of incoming uncertain exits',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->exits5Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'uncertainExitHash',
            );
        });

        # Tab complete
        return 1;
    }

    sub exits5Tab_refreshList {

        # Called by $self->exits4Tab, ->exits5Tab and ->exits6Tab to refresh the
        #   GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $iv         - The IV to show
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $iv, $check) = @_;

        # Local variables
        my (@exitNumList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $iv || defined $check) {

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

        # Import the IV
        @exitNumList = $self->editObj->ivKeys($iv);

        # Compile the simple list data
        foreach my $number (@exitNumList) {

            my ($exitObj, $roomObj, $regionObj);

            $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $number);
            $roomObj = $self->session->worldModelObj->ivShow('modelHash', $exitObj->parent);
            $regionObj = $self->session->worldModelObj->ivShow('modelHash', $roomObj->parent);

            push (@dataList,
                $roomObj->number,
                $regionObj->name,
                $exitObj->number,
                $exitObj->dir,
                $exitObj->mapDir,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub exits6Tab {

        # Exits6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Incoming one-way exits'],
        );

        # Incoming one-way exits
        $self->addLabel($grid, '<b>Incoming one-way exits</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of one-way exits belonging to other rooms which point to this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Room #', 'text',
            'Region', 'text',
            'Exit #', 'text',
            'Direction', 'text',
            'Map direction', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->exits5Tab_refreshList($slWidget, scalar (@columnList / 2), 'oneWayExitHash');

        # Add a single button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of incoming one-way exits',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->exits5Tab_refreshList($slWidget, scalar (@columnList / 2), 'oneWayExitHash');
        });

        # Tab complete
        return 1;
    }

    sub exits7Tab {

        # Exits7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Incoming involuntary exits'],
        );

        # Incoming involuntary exits
        $self->addLabel($grid, '<b>Incoming involuntary exits</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of rooms with involuntary exit patterns whose corresponding destination is'
            . ' this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Room #', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->exits7Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add a single button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of incoming involuntary exits',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->exits7Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub exits7Tab_refreshList {

        # Called by $self->exits6Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @dataList;

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

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

        # Import the IV
        @dataList = sort {$a <=> $b} ($self->editObj->ivValues('invRepExitHash'));

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub exits8Tab {

        # Exits8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Incoming random exits'],
        );

        # Incoming random exits
        $self->addLabel($grid, '<b>Incoming random exits</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of random exits belonging to other rooms which point to this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Room #', 'text',
            'Region', 'text',
            'Exit #', 'text',
            'Direction', 'text',
            'Map direction', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->exits5Tab_refreshList($slWidget, scalar (@columnList / 2), 'randomExitHash');

        # Add a single button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of incoming random exits',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->exits5Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'randomExitHash',
            );
        });

        # Tab complete
        return 1;
    }

    sub exits9Tab {

        # Exits9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _9',
            ['Checked directions'],
        );

        # Checked directions
        $self->addLabel($grid, '<b>Checked directions</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of directions the character has tried, but which generated a failed exit'
            . ' message</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Direction', 'text',
            'Number of failed attempts', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'checkedDirHash');

        # Add a single button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of checked directions',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'checkedDirHash');
        });

        # Tab complete
        return 1;
    }

    sub contentsTab {

        # Contents tab
        #
        # 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 . '->contentsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Contents');

        # Add tabs to the inner notebook
        $self->contents1Tab($innerNotebook);
        $self->contents2Tab($innerNotebook);
        $self->contents3Tab($innerNotebook);

        return 1;
    }

    sub contents1Tab {

        # Contents1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Temporary contents'],
        );

        # Temporary contents
        $self->addLabel($grid, '<b>Temporary contents</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of objects which are currently in a non-model room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            '#', 'text',
            'Object name', 'text',
            'Object category', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the simple list
        $self->contents1Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton(
            $grid,
            'View...',
            'View the selected temporary object',
            undef,
            1, 3, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            my (
                $num, $obj,
                @objList,
            );

            ($num) = $self->getSimpleListData($slWidget, 0);
            if (defined $num) {

                @objList = $self->getEditHash_listIV('tempObjList');
                $obj = $objList[$num - 1];
                if (defined $obj) {

                    # Open an 'edit' window for the exit object
                    $self->createFreeWin(
                        'Games::Axmud::EditWin::ModelObj::' . ucfirst($obj->category),
                        $self,
                        $self->session,
                        'Edit temporary ' . $obj->category . ' object',
                        $obj,
                        # Not temporary (at least not in the sense that 'edit' windows understand
                        #   an object to be temporary)
                        FALSE,
                    );
                }
            }

            # Refresh the simple list
            $self->contents1Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        my $button2 = $self->addButton(
            $grid,
            'Reset',
            'Reset the list of objects',
            undef,
            8, 10, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Remove the IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'tempObjList');

            # Refresh the simple list
            $self->contents1Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        my $button3 = $self->addButton(
            $grid,
            'Clear',
            'Empty the list of objects',
            undef,
            10, 12, 10, 11,
        );
        $button3->signal_connect('clicked' => sub {

            # Empty the list
            $self->ivAdd('editHash', 'tempObjList', []);

            # Refresh the simple list
            $self->contents1Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub contents1Tab_refreshList {

        # Called by $self->contents1Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $count,
            @objList, @dataList,
        );

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

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

        # Import the IV
        @objList = $self->getEditHash_listIV('tempObjList');

        # Compile the simple list data
        $count = 0;
        foreach my $obj (@objList) {

            $count++;

            push (@dataList,
                $count,
                $obj->name,
                $obj->category,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub contents2Tab {

        # Contents2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Hidden objects'],
        );

        # Hidden objects
        $self->addLabel($grid, '<b>Hidden objects</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of world model objects which are in the room, but not visible</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Object #', 'text',
            'Command to obtain it', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'hiddenObjHash');

        # Add entry boxes
        $self->addLabel($grid, 'Object #',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Command to obtain it (optional)',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'hiddenObjHash',
            10,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($number, $cmd);

            $number = $entry->get_text();
            $cmd = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Check the model object exists
                if (! $self->session->worldModelObj->ivExists('modelHash', $number)) {

                    $self->showMsgDialogue(
                        'Add hidden object',
                        'error',
                        'The object #' . $number . ' does not exist in the world model',
                        'ok',
                    );

                } else {

                    # Add a new key-value pair
                    $self->modifyEditHash_hashIV('hiddenObjHash', $number, $cmd);

                    # Reset the entry boxes
                    $self->resetEntryBoxes($entry, $entry2);
                }

                # In either case, refresh the list
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'hiddenObjHash');
            }
        });

        # Tab complete
        return 1;
    }

    sub contents3Tab {

        # Contents3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Optional arguments
        #   $tabTitle       - When inherited by GA::EditWin::Painter, the tab's page number (a
        #                       string)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

            return $axmud::CLIENT->writeImproper($self->_objClass . '->contents3Tab', @_);
        }
        # Tab setup
        if (! $tabTitle) {

            $tabTitle = 'Page _3';  # This function wasn't called by GA::EditWin::Painter
        }

        my $grid = $self->addTab(
            $innerNotebook,
            $tabTitle,
            ['Search responses'],
        );

        # Search responses
        $self->addLabel($grid, '<b>Search responses</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of non-interacting things in the room description which can be'
            . ' searched/examined</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Search term', 'text',
            'Result of search/examine', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'searchHash');

        # Add entry boxes
        $self->addLabel($grid, 'Thing\'s name',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($grid, 'Response (optional)',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', undef, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'searchHash',
            10,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($string, $response);

            $string = $entry->get_text();
            $response = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('searchHash', $string, $response);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'searchHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub protocolsTab {

        # Protocols tab
        #
        # 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 . '->protocolsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Pr_otocols');

        # Add tabs to the inner notebook
        $self->protocols1Tab($innerNotebook);
        $self->protocols2Tab($innerNotebook);

        return 1;
    }

    sub protocols1Tab {

        # Protocols1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Optional arguments
        #   $tabTitle       - When inherited by GA::EditWin::Painter, the tab's page number (a
        #                       string)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @comboList);

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

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

        # Tab setup
        if (! $tabTitle) {

            $tabTitle = 'Page _1';  # This function wasn't called by GA::EditWin::Painter
        }

        my $grid = $self->addTab(
            $innerNotebook,
            $tabTitle,
            ['Room data'],
        );

        # Room data
        $self->addLabel($grid, '<b>Room data</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Data for this room supplied by various MUD protocols</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Name', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'protocolRoomHash');

        # Tab complete
        return 1;
    }

    sub protocols2Tab {

        # Protocols2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Optional arguments
        #   $tabTitle       - When inherited by GA::EditWin::Painter, the tab's page number (a
        #                       string)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@columnList, @comboList);

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

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

        # Tab setup
        if (! $tabTitle) {

            $tabTitle = 'Page _2';  # This function wasn't called by GA::EditWin::Painter
        }

        my $grid = $self->addTab(
            $innerNotebook,
            $tabTitle ,
            ['Exit data'],
        );

        # Exit data
        $self->addLabel($grid, '<b>Exit data</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Data for this room\'s exits supplied by various MUD protocols</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Direction', 'text',
            'Destination vnum', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'protocolExitHash');

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Weapon;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Weapon')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 4
        $self->physicalTab();
        $self->exclusiveTab();
        # Group 5
        #   (no group 5 IVs for weapons)

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Armour;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Armour')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 4
        $self->physicalTab();
        $self->exclusiveTab();
        # Group 5
        #   (no group 5 IVs for armours)

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Garment;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Garment')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 4
        $self->physicalTab();
        $self->exclusiveTab();
        # Group 5
        #   (no group 5 IVs for garments)

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Char;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Char')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 3
        $self->attacksTab();
        # Group 5
        $self->characterTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    sub characterTab {

        # Creature tab
        #
        # 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 . '->characterTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'C_haracter');

        # Add tabs to the inner notebook (uses some of the same tabs as sentients)
        $self->character1Tab($innerNotebook);
        $self->sentient2Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Minion;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Minion')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 3
        $self->attacksTab();
        # Group 5
        $self->minionTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    sub minionTab {

        # Minion tab
        #
        # 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 . '->minionTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Minion');

        # Add tabs to the inner notebook (uses some of the same tabs as sentients and characters)
        $self->character1Tab($innerNotebook);
        $self->sentient2Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Sentient;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Sentient')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 3
        $self->attacksTab();
        # Group 5
        $self->sentientTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    sub sentientTab {

        # Sentient tab
        #
        # 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 . '->sentientTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Sentient');

        # Add tabs to the inner notebook
        $self->sentient1Tab($innerNotebook);
        $self->sentient2Tab($innerNotebook);
        $self->sentient3Tab($innerNotebook);
        $self->sentient4Tab($innerNotebook);
        $self->sentient5Tab($innerNotebook);
        $self->sentient6Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Creature;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Creature')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 3
        $self->attacksTab();
        # Group 5
        $self->creatureTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    sub creatureTab {

        # Creature tab
        #
        # 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 . '->creatureTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'C_reature');

        # Add tabs to the inner notebook
        $self->sentient1Tab($innerNotebook);
        $self->sentient2Tab($innerNotebook);
        $self->sentient3Tab($innerNotebook);
        $self->sentient4Tab($innerNotebook);
        $self->sentient5Tab($innerNotebook);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Portable;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud

    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Portable')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 4
        $self->physicalTab();
        $self->exclusiveTab();
        # Group 5
        $self->portableTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    sub portableTab {

        # Portable tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'Po_rtable',
            ['Properties belonging only to portable model objects'],
        );

        # Properties belonging only to portable world model objects
        $self->addLabel($grid, '<b>Properties belonging only to portable model objects</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Object\'s type (further classifies the type of portable object)',
            1, 8, 1, 2);
        @comboList = $self->session->currentDict->portableTypeList;
        $self->addComboBox($grid, 'type', \@comboList, '',
            TRUE,               # No 'undef' value used
            8, 12, 1, 2);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Decoration;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Decoration')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 4
        $self->physicalTab();
        $self->exclusiveTab();
        # Group 5
        $self->decorationTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    sub decorationTab {

        # Decoration tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Decoration',
            ['Properties belonging only to decoration model objects'],
        );

        # Properties belonging only to decoration world model objects
        $self->addLabel($grid, '<b>Properties belonging only to decoration model objects</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Object\'s type (further classifies the type of decoration object)',
            1, 8, 1, 2);
        @comboList = $self->session->currentDict->decorationTypeList;
        $self->addComboBox($grid, 'type', \@comboList, '',
            TRUE,               # No 'undef' value used
            8, 12, 1, 2);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::ModelObj::Custom;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::ModelObj Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin
        Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Custom')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of model object is
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Group 1
        $self->familyTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private properties hash',
            'Properties created by your own plugins and scripts, but stored in this type of object',
        );
        $self->notesTab();
        # Group 2
        $self->wordsTab();
        # Group 4
        $self->physicalTab();
        $self->exclusiveTab();
        # Group 5
        #   (no group 5 IVs for custom model objects)

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Group 5

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Painter;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::ModelObj::Room Games::Axmud::EditWin::Generic::ModelObj
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

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

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

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

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::ModelObj::Room')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->roomTab();

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->contentsTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Redraw menu bars/toolbars in all automapper windows using this world model
            $self->session->worldModelObj->updateMapMenuToolbars();
        }

        return 1;
    }

    # Notebook tabs

    sub roomTab {

        # Room tab
        #
        # 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 . '->roomTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Room');

        # Add tabs to the inner notebook (mostly inherited from GA::EditWin::ModelObj::Room, so we
        #   pass an extra argument to show the tab which page number to display)
        $self->ownRoom1Tab($innerNotebook);              # (not inherited)
        $self->room2Tab($innerNotebook, 'Page _2');     # ->roomFlagHash
        $self->room4Tab($innerNotebook, 'Page _3');     # ->descripHash
        $self->ownRoom2Tab($innerNotebook);              # (not inherited)
        $self->room7Tab($innerNotebook, 'Page _5');     # ->exclusiveFlag, ->exclusiveHash

        return 1;
    }

    sub ownRoom1Tab {

        # OwnRoom1Tab tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@guildList, @comboList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Wilderness mode'],
        );

        # Wilderness mode
        $self->addLabel($grid, '<b>Wilderness mode</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'normal\'', 'wildMode',
            'normal',          # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel(
            $grid,
            "The automapper expects an exit to exist in the world model for every exit in this"
            . " room\n(if an exit is visible in the automapper window, it exists in the world"
            . " model)",
            4, 12, 1, 2);
        $self->addLabel(
            $grid,
            "<i>In \'update\' mode, the automapper window draws new exits</i>",
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'wild\'', 'wildMode', 'wild', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $grid,
            "The automapper assumes the world doesn\'t specify any exits for this room and that"
            . " the\ncharacter can move from this room to any adjacent room using any primary"
            . " direction",
            4, 12, 3, 4);
        $self->addLabel(
            $grid,
            '<i>In \'update\' mode, the automapper window doesn\'t draw new exits</i>',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'border\'', 'wildMode', 'border', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $grid,
            "The automapper assumes the world doesn\'t specify any exits for this room and that"
            . " the\ncharacter can move to any adjacent \'wild\' or \'border\' room, but movement"
            . " to a \'normal\'\nroom requires an exit in the world model",
            4, 12, 5, 6);
        $self->addLabel(
            $grid,
            "<i>In \'update\' mode, the automapper window only draws exits between a \'border\'"
            . " room and\nan adjacent \'normal\' room</i>",
            4, 12, 6, 7);

        # Tab complete
        return 1;
    }

    sub ownRoom2Tab {

        # OwnRoom2Tab tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (@guildList, @comboList);

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Room titles', 'Room guild'],
        );

        # Room titles
        $self->addLabel($grid, '<b>Room titles</b>',
            0, 12, 0, 2);
        $self->addLabel($grid, '<i>List of all known titles for this room</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'titleList', TRUE,
            1, 12, 4, 6);

        # Room guild
        $self->addLabel($grid, '<b>Room guild</b>',
            0, 12, 6, 8);

        # Get a sorted list of existing guild profiles
        foreach my $profile ($self->session->ivValues('profHash')) {

            if ($profile->category eq 'guild') {

                push (@guildList, $profile->name);
            }
        }

        @comboList = sort {lc($a) cmp lc($b)} (@guildList);

        my $combo = $self->addComboBox($grid, 'roomGuild', \@comboList, 'Choose a guild:',
            FALSE,              # 'undef' value allowed
            1, 6, 8, 10);

        # Tab complete
        return 1;
    }

    sub contentsTab {

        # Contents tab
        #
        # 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 . '->contentsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Contents');

        # Add tabs to the inner notebook (inherited from GA::EditWin::ModelObj::Room, so we pass an
        #   extra argument to show the tab which page number to display)
        $self->contents3Tab($innerNotebook, 'Page _1');     # ->searchHash

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Generic::Profile;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

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

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

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

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

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

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

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

    # ->signal_connects

    # Other functions

#   sub checkEditObj {}         # Inherited from GA::Generic::ConfigWin

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook, once the user has specified the profile's name
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Profile name'],
        );

        # Profile name
        $self->addLabel($grid, '<b>Profile name</b>',
            0, 6, 0, 1);

        # Explanatory label - contents to be set to 'Choose a name and press enter', in a moment
        my $label = $self->addLabel($grid, '',
            1, 6, 2, 3);

        # Entry box for the profile name
        my $entry;

        if ($self->tempFlag) {

            # $self->editObj is a temporary profile, so the entry box must be sensitive
            $entry = $self->addEntry($grid, undef, TRUE,
                1, 6, 1, 2, 16, 16);

            $entry->signal_connect (activate => sub {

                my $text = $entry->get_text();

                # Check that the name is acceptable
                if (
                    (
                        $self->editObj->category eq 'world'
                        && $axmud::CLIENT->ivExists('worldProfHash', $text)
                    ) || (
                        $self->editObj->category ne 'world'
                        && $self->session->ivExists('profHash', $text)
                    )
                ) {
                    $label->set_markup('<i>Profile named \'' . $text . '\' already exists</i>');
                    $self->winShowAll($self->_objClass . '->setupNotebook');

                } elsif (! $axmud::CLIENT->nameCheck($text, 16)) {

                    $label->set_markup('<i>Max 16 chars: A-Z a-z _ 0-9</i>');
                    $self->winShowAll($self->_objClass . '->setupNotebook');

                } else {

                    # Replace the explanatory label
                    $label->set_markup('<i>You may now edit the profile</i>');
                    # Desensitise the entry box - once the profile name is entered, it's final
                    $entry->set_sensitive(FALSE);
                    # Make sure the OK/reset/save buttons are now sensitised
                    $self->okButton->set_sensitive(TRUE);
                    $self->resetButton->set_sensitive(TRUE);
                    $self->saveButton->set_sensitive(TRUE);

                    # Store the name until all IVs are copied to the profile
                    $self->ivAdd('editHash', 'name', $text);

                    # Set up the remaining tabs
                    $self->expandNotebook();

                    # Render the changes
                    $self->winShowAll($self->_objClass . '->setupNotebook');
                }
            });

            $entry->signal_connect('changed' => sub {

                my ($value, $result);

                $value = $entry->get_text();
                # Check whether $text is a valid value, or not
                if ($axmud::CLIENT->nameCheck($value, 16)) {
                    $entry->set_icon_from_stock('secondary', 'gtk-yes');
                } else {
                    $entry->set_icon_from_stock('secondary', 'gtk-no');
                }
            });

            $label->set_markup('<i>Choose a name and press \'enter\'</i>');
            # (Entry is initially empty)
            $entry->set_icon_from_stock('secondary', 'gtk-no');

        } else {

            # $self->editObj is an existing profile, so the entry box must be insensitive
            $entry = $self->addEntry($grid, 'name', FALSE,
                1, 6, 1, 2, 16, 16);

            $label->set_markup('<i>(The profile name can\'t be changed)</i>');

            # Set up the remaining tabs
            $self->expandNotebook();
        }

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves the changes made to the profile. If it was a temporary profile, create a real
        #   profile, and save the changes to the latter
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # For a temporary profile...
            if ($self->tempFlag) {

                my ($profName, $result, $profObj, $number);

                $profName = $self->ivShow('editHash', 'name');

                # The user made changes, so we can now create a permanent profile and give it IVs
                #   specified in $self->editHash
                if ($self->editObj->category eq 'world') {

                    $result = $self->session->pseudoCmd(
                        'addworld ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } elsif ($self->editObj->category eq 'guild') {

                    $result = $self->session->pseudoCmd(
                        'addguild ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } elsif ($self->editObj->category eq 'race') {

                    $result = $self->session->pseudoCmd(
                        'addrace ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } elsif ($self->editObj->category eq 'char') {

                    $result = $self->session->pseudoCmd(
                        'addchar ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $result = $self->session->pseudoCmd(
                        'addcustomprofile ' . $profName . ' ' . $self->editObj->category,
                        $self->pseudoCmdMode,
                    );
                }

                if ($result) {

                    # Get the blessed reference of the new profile
                    if ($self->editObj->category eq 'world') {
                        $profObj = $axmud::CLIENT->ivShow('worldProfHash', $profName);
                    } else {
                        $profObj = $self->session->ivShow('profHash', $profName);
                    }

                    # Store the changes the user has made (ignore the 'name' IV, which has aleady
                    #   been set)
                    $self->ivDelete('editHash', 'name');

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

                        $profObj->{$key} = $self->ivShow('editHash', $key);
                    }

                    # If the 'save' button was clicked, the window won't be closed. Before
                    #   re-drawing all those tabs, set the object being edited to the one we just
                    #   created
                    $self->ivPoke('editObj', $profObj);
                    $self->ivPoke('tempFlag', FALSE);
                }

            # For an existing profile...
            } else {

                # Store the changes the user has made
                foreach my $key ($self->ivKeys('editHash')) {

                    # Special case: for GA::Profile::World->worldCharSet IV, must use a client
                    #   command
                    if ($self->editObj->category eq 'world' && $key eq 'worldCharSet') {

                        $self->session->pseudoCmd(
                            'setcharset -w ' . $self->ivShow('editHash', $key),
                            $self->pseudoCmdMode,
                        );

                    } else {

                        $self->editObj->{$key} = $self->ivShow('editHash', $key);
                    }
                }
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs used by more than one profile 'edit' window

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid  -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup (already created by the calling function)

        # Profile category
        $self->addLabel($grid, '<b>Profile category</b>',
            0, 6, 3, 4);
        $self->addEntry($grid, 'category', FALSE,
            1, 6, 4, 5, 16, 16);

        # Parent world profile
        $self->addLabel($grid, '<b>Parent world profile</b>',
            7, 13, 0, 1);
        $self->addEntry($grid, 'parentWorld', FALSE,
            8, 13, 1, 2);

        # Current profile
        $self->addLabel($grid, '<b>Current profile</b>',
            7, 13, 3, 4);

        my $checkButton = Gtk3::CheckButton->new();
        if (
            $self->session->ivExists('currentProfHash', $self->editObj->category)
            && $self->session->ivShow('currentProfHash', $self->editObj->category) eq $self->editObj
        ) {
            $checkButton->set_active(TRUE);
        }
        $checkButton->set_sensitive(FALSE);
        $grid->attach($checkButton, 8, 4, 5, 1);

        # Tab complete
        return 1;
    }

    sub commandsTab {

        # Commands tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @list;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Commands',
            ['List of profile-specific commands'],
        );

        # List of profile-specific commands
        $self->addLabel($grid, '<b>List of profile-specific commands</b>',
            0, 12, 0, 2);
        $self->addLabel($grid, '<i>(Character-independent)</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'cmdList', TRUE,
            1, 12, 4, 6);

        # Tab complete
        return 1;
    }

    sub fightTab {

        # Fight tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Fight',
            ['List of profile-specific fight patterns'],
        );

        # List of profile-specific fight patterns
        $self->addLabel($grid, '<b>List of profile-specific fight patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen during a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'fightMsgList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'fightMsgList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV('fightMsgList', undef, FALSE, $pattern, $grpNum);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'fightMsgList');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub interactionTab {

        # Interaction tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'Int_eraction',
            ['List of profile-specific interaction patterns'],
        );

        # List of profile-specific interaction patterns
        $self->addLabel($grid, '<b>List of profile-specific interaction patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen during an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'interactionMsgList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'interactionMsgList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'interactionMsgList',
                    undef,
                    FALSE,
                    $pattern,
                    $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionMsgList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub initialTab {

        # Initial tab
        #
        # 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 . '->initialTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Initial');

        # Add tabs to the inner notebook
        $self->initial1Tab($innerNotebook);
        $self->initial2Tab($innerNotebook);
        $self->initial3Tab($innerNotebook);

        return 1;
    }

    sub initial1Tab {

        # Initial1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @columnList;

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Initial tasks'],
        );

        # Initial tasks
        $self->addLabel($grid, '<b>Initial tasks</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of initial tasks that start when this profile becomes current</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Name' => 'text',       # ->uniqueName
            'Type' => 'text',       # ->category
            'Jealous' => 'bool',    # ->jealousyFlag
            'Stage' => 'text',      # ->stage
            'Status' => 'text',     # ->status
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->initial1Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing buttons and combos
        my $combo = $self->addComboBox($grid, undef, [], '',
            TRUE,           # No 'undef' value used
            1, 4, 10, 11);
        $self->initial1Tab_refreshCombo($combo);

        my $button = $self->addButton(
            $grid,
            'Add initial task...',
            'Add the selected task as an initial task',
            undef,
            4, 6, 10, 11,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($taskName, $childWinObj);

            $taskName = $combo->get_active_text();
            if (defined $taskName) {

                # Open up a task start 'pref' window to specify task settings
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::PrefWin::TaskStart',
                    $self,
                    $self->session,
                    '\'' . $taskName . '\' task preferences',
                    undef,                                  # No ->editObj
                    FALSE,                                  # Not temporary
                    # Config
                    'type'  => 'profile_initial',           # Profile initial tasklist
                    'task_name' => $taskName,
                    'prof_name' => $self->editObj->name,    # Parent profile
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'initial1Tab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );

                    $self->add_childDestroy(
                        $childWinObj,
                        'initial1Tab_refreshCombo',
                        [$combo],
                    );
                }
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Edit...',
            'Edit the selected task',
            undef,
            6, 8, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            my ($taskName, $taskObj);

            ($taskName) = $self->getSimpleListData($slWidget, 0);
            if (defined $taskName) {

                $taskObj = $self->editObj->ivShow('initTaskHash', $taskName);

                $self->createFreeWin(
                    'Games::Axmud::EditWin::Task',
                    $self,
                    $self->session,
                    'Edit ' . $taskObj->prettyName . ' task',
                    $taskObj,
                    FALSE,                          # Not temporary
                    # Config
                    'edit_flag' => TRUE,            # Some IVs for initial tasks are editable
                );
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Move up',
            'Move the selected initial task up the list',
            undef,
            8, 10, 10, 11,
            TRUE,               # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my (
                $taskName, $match, $count,
                @orderList, @modList,
            );

            ($taskName) = $self->getSimpleListData($slWidget, 0);
            if (defined $taskName) {

                # Import the existing order list
                @orderList = $self->editObj->initTaskOrderList;

                # Remove the selected task from the list
                $count = -1;
                foreach my $item (@orderList) {

                    $count++;

                    if ($item ne $taskName || $count == 0) {

                        push (@modList, $item);

                    } else {

                        $match = $count;
                    }
                }

                if (defined $match) {

                    # A task was removed. Replace it one place higher
                    $match--;
                    splice (@modList, $match, 0, $taskName);
                }

                # Update the list
                $self->editObj->ivPoke('initTaskOrderList', @modList);

                # Refresh the simple list and select the same line
                $self->initial1Tab_refreshList($slWidget, (scalar @columnList / 2));
                if (defined $match) {

                    $slWidget->select($match);
                }

                # Also refresh the combo
                $self->initial1Tab_refreshCombo($combo);
            }
        });

        my $button4 = $self->addButton(
            $grid,
            'Move down',
            'Move the selected initial task down the list',
            undef,
            10, 12, 10, 11,
            TRUE,               # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my (
                $taskName, $match, $count,
                @orderList, @modList,
            );

            ($taskName) = $self->getSimpleListData($slWidget, 0);
            if (defined $taskName) {

                # Import the existing order list
                @orderList = $self->editObj->initTaskOrderList;

                # Remove the selected script from the list
                $count = -1;
                foreach my $item (@orderList) {

                    $count++;

                    if ($item ne $taskName || $count == (scalar (@orderList) - 1)) {

                        push (@modList, $item);

                    } else {

                        $match = $count;
                    }
                }

                if (defined $match) {

                    # A task was removed. Replace it one place lower
                    $match++;
                    splice (@modList, $match, 0, $taskName);
                }

                # Update the list
                $self->editObj->ivPoke('initTaskOrderList', @modList);

                # Refresh the simple list and select the same line
                $self->initial1Tab_refreshList($slWidget, (scalar @columnList / 2));
                if (defined $match) {

                    $slWidget->select($match);
                }

                # Also refresh the combo
                $self->initial1Tab_refreshCombo($combo);
            }
        });

        my $button5 = $self->addButton(
            $grid,
            'Delete',
            'Delete the selected task',
            undef,
            1, 3, 11, 12,
            TRUE,               # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            my ($taskName, $taskObj);

            ($taskName) = $self->getSimpleListData($slWidget, 0);
            if (defined $taskName) {

                $taskObj = $self->editObj->ivShow('initTaskHash', $taskName);

                $self->session->pseudoCmd(
                    'deleteinitialtask ' . $taskObj->uniqueName . ' ' . $taskObj->profName,
                    $self->pseudoCmdMode,
                );

                # Reset the simple list
                $self->initial1Tab_refreshList($slWidget, (scalar @columnList / 2));
                # Also refresh the combo
                $self->initial1Tab_refreshCombo($combo);
            }
        });

        my $button6 = $self->addButton(
            $grid,
            'Delete all',
            'Delete all of this profile\'s initial tasks',
            undef,
            3, 5, 11, 12,
            TRUE,           # Irreversible
        );
        $button6->signal_connect('clicked' => sub {

            $self->session->pseudoCmd(
                'deleteinitialtask -a ' . $self->editObj->name,
                $self->pseudoCmdMode,
            );

            # Reset the simple list
            $self->initial1Tab_refreshList($slWidget, (scalar @columnList / 2));
            # Also refresh the combo
            $self->initial1Tab_refreshCombo($combo);
        });

        my $button7 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of initial tasks',
            undef,
            7, 10, 11, 12,
        );
        $button7->signal_connect('clicked' => sub {

            # Reset the simple list
            $self->initial1Tab_refreshList($slWidget, (scalar @columnList / 2));
            # Also refresh the combo, just in case the user has added a new type of task (very
            #   unlikely)
            $self->initial1Tab_refreshCombo($combo);
        });

        my $button8 = $self->addButton(
            $grid,
            'Dump',
            'Display this profile\'s list of initial tasks in the \'main\' window',
            undef,
            10, 12, 11, 12,
        );
        $button8->signal_connect('clicked' => sub {

            $self->session->pseudoCmd(
                'listinitialtask ' . $self->editObj->name,
                $self->pseudoCmdMode,
            );

            # Reset the simple list
            $self->initial1Tab_refreshList($slWidget, (scalar @columnList / 2));
            # Also refresh the combo
            $self->initial1Tab_refreshCombo($combo);
        });

        # Tab complete
        return 1;
    }

    sub initial1Tab_refreshList {

        # Called by $self->initial1Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - Number of columns in the simple list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @dataList;

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

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

        # Compile the simple list data
        OUTER: foreach my $taskName ($self->editObj->initTaskOrderList) {

            my $taskObj = $self->editObj->ivShow('initTaskHash', $taskName);

            push (@dataList,
                $taskObj->uniqueName, $taskObj->category, $taskObj->jealousyFlag,
                $taskObj->stage, $taskObj->status,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub initial1Tab_refreshCombo {

        # Called by $self->initial1Tab to refresh the Gtk3::Combo
        #
        # Expected arguments
        #   $combo      - The combo box to refresh
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @comboList,
            %packageHash,
        );

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

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

        # For each task package in GA::Client->taskPackageHash, create a temporary task so that we
        #   can access its ->storableFlag IV (only storable tasks can appear in the combo)
        %packageHash = $axmud::CLIENT->taskPackageHash;

        foreach my $name (sort {lc($a) cmp lc($b)} (keys %packageHash)) {

            my ($package, $tempObj);

            $package = $packageHash{$name};

            # Create the temporary task
            $tempObj = $package->new($self->session);
            if ($tempObj && $tempObj->storableFlag) {

                push (@comboList, $name);
            }
        }

        $self->resetComboBox($combo, @comboList);

        return 1;
    }

    sub initial2Tab {

        # Initial2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @columnList, @initList, @comboList,
            %comboHash,
        );

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

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

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Initial scripts'],
        );

        # Initial scripts
        $self->addLabel($grid, '<b>Initial scripts</b>',
            0, 13, 0, 1);
        $self->addLabel($grid,
            '<i>List of initial scripts that start when this profile becomes current</i>',
            1, 13, 1, 2);

        # Add a simple list
        @columnList = (
            'Script' => 'text',
            'Run mode' => 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 13, 2, 8);

        # Initialise the list
        $self->initial2Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing entry boxes/buttons
        $self->addLabel($grid, 'Script name',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 7, 8, 9);

        $self->addLabel($grid, 'Run mode',
            7, 9, 8, 9);

        @initList = (
            'no_task'       => 'Run without a task',
            'run_task'      => 'Run from within a task',
            'run_task_win'  => 'Run in \'forced window\' mode',
        );

        do {

            my ($value, $string, $descrip);

            $value = shift @initList;
            $string = shift @initList;
            $descrip = 'Mode \'' . $value . '\' - ' . $string;

            push (@comboList, $descrip);
            $comboHash{$descrip} = $value;

        } until (! @initList);

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            9, 13, 8, 9);

        my $button = $self->addButton(
            $grid,
            'Add',
            'Add this script to the list of initial scripts',
            undef,
            1, 3, 9, 10,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($scriptName, $descrip, $mode, $switch);

            if ($self->checkEntryIcon($entry)) {

                $scriptName = $entry->get_text();
                $descrip = $combo->get_active_text();
                $mode = $comboHash{$descrip};

                if ($mode eq 'no_task') {
                    $switch = '-r';
                } elsif ($mode eq 'run_task') {
                    $switch = '-t';
                } else {
                    $switch = '-c';
                }

                $self->session->pseudoCmd(
                    'addinitialscript ' . $scriptName . ' ' . $self->editObj->name . ' ' . $switch,
                    $self->pseudoCmdMode,
                );

                # Reset the simple list and reset the entry box
                $self->initial2Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Edit',
            'Open the selected script in a text editor',
            undef,
            3, 6, 9, 10,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            my ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                $self->session->pseudoCmd('editscript ' . $name, $self->pseudoCmdMode);

                # Reset the simple list and reset the entry box
                $self->initial2Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Delete',
            'Delete the selected script',
            undef,
            6, 8, 9, 10,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                $self->session->pseudoCmd(
                    'deleteinitialscript ' . $name . ' ' . $self->editObj->name,
                    $self->pseudoCmdMode,
                );

                # Reset the simple list and reset the entry box
                $self->initial2Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button4 = $self->addButton($grid,
            'Move up', 'Move the selected script up the list', undef,
            9, 11, 9, 10,
            TRUE);      # Irreversible
        $button4->signal_connect('clicked' => sub {

            my (
                $script, $match, $count,
                @orderList, @modList,
            );

            ($script) = $self->getSimpleListData($slWidget, 0);
            if (defined $script) {

                # Import the existing order list
                @orderList = $self->editObj->initScriptOrderList;

                # Remove the selected script from the list
                $count = -1;
                foreach my $item (@orderList) {

                    $count++;

                    if ($item ne $script || $count == 0) {

                        push (@modList, $item);

                    } else {

                        $match = $count;
                    }
                }

                if (defined $match) {

                    # A script was removed. Replace it one place higher
                    $match--;
                    splice (@modList, $match, 0, $script);
                }

                # Update the list
                $self->editObj->ivPoke('initScriptOrderList', @modList);

                # Refresh the simple list and select the same line
                $self->initial2Tab_refreshList($slWidget, scalar (@columnList / 2));
                if (defined $match) {

                    $slWidget->select($match);
                }
            }
        });

        my $button5 = $self->addButton($grid,
            'Move down', 'Move the selected script down the list', undef,
            11, 13, 9, 10,
            TRUE);          # Irreversible
        $button5->signal_connect('clicked' => sub {

            my (
                $script, $match, $count,
                @orderList, @modList,
            );

            ($script) = $self->getSimpleListData($slWidget, 0);
            if (defined $script) {

                # Import the existing order list
                @orderList = $self->editObj->initScriptOrderList;

                # Remove the selected script from the list
                $count = -1;
                foreach my $item (@orderList) {

                    $count++;

                    if ($item ne $script || $count == (scalar (@orderList) - 1)) {

                        push (@modList, $item);

                    } else {

                        $match = $count;
                    }
                }

                if (defined $match) {

                    # A script was removed. Replace it one place lower
                    $match++;
                    splice (@modList, $match, 0, $script);
                }

                # Update the list
                $self->editObj->ivPoke('initScriptOrderList', @modList);

                # Refresh the simple list and select the same line
                $self->initial2Tab_refreshList($slWidget, scalar (@columnList / 2));
                if (defined $match) {

                    $slWidget->select($match);
                }
            }
        });

        my $button6 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of initial scripts',
            undef,
            9, 11, 10, 11);
        $button6->signal_connect('clicked' => sub {

            # Reset the simple list
            $self->initial2Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button7 = $self->addButton(
            $grid,
            'Dump',
            'Display this profile\'s list of initial tasks in the \'main\' window',
            undef,
            11, 13, 10, 11);
        $button7->signal_connect('clicked' => sub {

            # Display initial scripts
            $self->session->pseudoCmd(
                'listinitialscript ' . $self->editObj->name,
                $self->pseudoCmdMode,
            );

            # Reset the simple list
            $self->initial2Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub initial2Tab_refreshList {

        # Called by $self->initial2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - Number of columns in the simple list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->initial2Tab_refreshList',
                @_,
            );
        }

        # Compile the simple list data
        OUTER: foreach my $scriptName ($self->editObj->initScriptOrderList) {

            push (@dataList,
                $scriptName,
                $self->editObj->ivShow('initScriptHash', $scriptName),
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub initial3Tab {

        # Initial3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->initial3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Initial mission', 'Initial command list'],
        );

        # Initial mission
        # (NB Labels stop at column 11; otherwise the 'undef' button takes up most of the row,
        #   rather than being positioned at the end, as it should be)
        $self->addLabel($grid, '<b>Initial mission</b>',
            0, 11, 0, 1);
        $self->addLabel($grid,
            '<i>The mission to start when a session begins with this as a current profile</i>',
            1, 11, 1, 2);
        $self->addLabel($grid,
            '<i>(N.B. Current profiles are checked in priority order; only the first mission'
            . ' found is started)</i>',
            1, 11, 2, 3);
        $self->addEntryWithButton($grid, 'initMission', TRUE,
            1, 12, 3, 4);

        # Initial command list
        $self->addLabel($grid, '<b>Initial command list</b>',
            0, 11, 4, 5);
        $self->addLabel($grid,
            '<i>List of commands executed when a session begins with this as a current'
            . ' profile (N.B. current profiles</i>',
            1, 11, 5, 6);
        $self->addLabel($grid,
            '<i>are checked in priority order; commands are sent from all'
            . ' profiles but no duplicate commands are sent</i>',
            1, 11, 6, 7);
        $self->addTextView($grid, 'initCmdList', TRUE,
            1, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE,
        );

        # Tab complete
        return 1;
    }

    sub notesTab {

        # Notes tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->notesTab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, 'N_otes');

        # Notes
        $self->addLabel($grid, '<b>Notes</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Persistent notes for this profile (also used by the Notepad task)</i>',
            1, 12, 1, 2);

        my $textView = $self->addTextView($grid, 'notepadList', TRUE,
            1, 12, 2, 11,
            TRUE, FALSE, FALSE, FALSE,     # Treat as a list, don't remove empty lines or whitespace
        );
        my $button = $self->addButton($grid,
            'Clear notes', 'Empty the persistent notes for this profile', undef,
            10, 12, 11, 12);
        $button->signal_connect('clicked' => sub {

            $textView->get_buffer->set_text('');
        });

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Profile::World;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Profile Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Profile::World')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of profile is being
        #   being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # The combobox in $self->rooms2Tab (used by 'Rooms' pages 2, 4 and 6) must be updated
        #   whenever the user adds/deletes a room statement component (in page 1). Use a special IV
        #   to store the comboboxes to update
        $self->{comboHash} = {};
        # The simple lists on these tabs must also be updated, when the user deletes a component on
        #   page 1. Use a special IV to store the simple lists to  update
        $self->{simpleListHash} = {};

        # If this is a current profile, set a flag
        if ($self->session->currentWorld eq $self->editObj) {

            $self->ivPoke('currentFlag', TRUE);
        }

        # Expand the notebook
        $self->settingsTab();
        $self->initialTab();            # Inherited from GA::EditWin::Generic::Profile
        $self->notesTab();              # Inherited from GA::EditWin::Generic::Profile
        $self->overrideTab();
        $self->roomsTab();
        $self->movesTab();
        $self->statusTab();
        $self->inventoryTab();
        $self->channelsTab();
        $self->attackTab();
        $self->worldFightTab();         # (Not inherited from GA::EditWin::Generic::Profile)
        $self->worldInteractionTab();   # (Not inherited from GA::EditWin::Generic::Profile)
        $self->worldCommandsTab();      # (Not inherited from GA::EditWin::Generic::Profile)
        $self->advanceTab();
        $self->currencyTab();
        $self->missionsTab();
        $self->questsTab();
        $self->statsTab();
        $self->privateDataTab(          # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private data hash',
            'Data created by your own plugins and scripts, but stored in this profile',
        );

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # If there's a Channels/Divert task running, and the user has just changed some of
            #   the channel pattern IVs, tell the task to update its triggers
            if (
                $self->editObj->category eq 'world'
                && (
                    $self->ivExists('editHash', 'channelList')
                    || $self->ivExists('editHash', 'noChannelList')
                )
            ) {
                if (
                    $self->session->divertTask
                    && (
                        $self->session->divertTask->status eq 'running'
                        || $self->session->divertTask->status eq 'paused'
                    )
                    && $self->session->divertTask->stage > 2
                ) {
                    $self->session->divertTask->resetTriggers();

                # Channels/Divert tasks can't run at the same time
                } elsif (
                    $self->session->channelsTask
                    && (
                        $self->session->channelsTask->status eq 'running'
                        || $self->session->channelsTask->status eq 'paused'
                    )
                ) {
                    $self->session->channelsTask->resetTriggers();
                }
            }

            # If ->termOverrideHash has been updated, inform all sessions whose current world is
            #   this one
            if (
                $self->editObj->category eq 'world'
                && $self->ivExists('editHash', 'termOverrideHash')
            ) {
                foreach my $otherSession ($axmud::CLIENT->ivValues('sessionHash')) {

                    if ($otherSession->currentWorld eq $self->editObj) {

                        $self->session->textViewCursorUpdate();
                        $self->session->textViewKeysUpdate();
                    }
                }
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Changes to ->basicMappingFlag effect menu bar/toolbar items in automapper windows,
            #   so update them
            foreach my $session ($axmud::CLIENT->findSessions($self->session->currentWorld->name)) {

                if ($session->mapWin) {

                    $session->mapWin->restrictWidgets();
                }
            }
        }

        # The most efficient way to keep ->verboseExitDelimiterList, etc in the right order
        #   (longest first) is to sort them, every time this window is saved
        $self->editObj->ivPoke(
            'verboseExitDelimiterList',
                sort {length ($b) <=> length ($a)} ($self->editObj->verboseExitDelimiterList)
        );

        $self->editObj->ivPoke(
            'briefExitDelimiterList',
                sort {length ($b) <=> length ($a)} ($self->editObj->briefExitDelimiterList)
        );

        $self->editObj->ivPoke(
            'roomCmdDelimiterList',
                sort {length ($b) <=> length ($a)} ($self->editObj->roomCmdDelimiterList)
        );

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -   The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Profile category
        $self->addLabel($grid, '<b>Profile category</b>',
            0, 6, 3, 4);
        $self->addEntry($grid, 'category', FALSE,
            1, 6, 4, 5, 16, 16);

        # World long name
        $self->addLabel($grid, '<b>World long name</b>',
            7, 13, 0, 1);
        $self->addEntryWithIcon($grid, 'longName', 'string', 1, undef,
            8, 13, 1, 2);

        $self->addLabel($grid, '<i>No length or character limits</i>',
            8, 13, 2, 3);

        # Current profile
        $self->addLabel($grid, '<b>Current profile</b>',
            7, 13, 3, 4);

        my $checkButton = Gtk3::CheckButton->new();
        if (
            $self->session->ivExists('currentProfHash', $self->editObj->category)
            && $self->session->ivShow('currentProfHash', $self->editObj->category) eq $self->editObj
        ) {
            $checkButton->set_active(TRUE);
        }
        $checkButton->set_sensitive(FALSE);
        $grid->attach($checkButton, 8, 4, 5, 1);

        # Tab complete
        return 1;
    }

    sub settingsTab {

        # Settings tab
        #
        # 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 . '->settingsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Settin_gs');

        # Add tabs to the inner notebook
        $self->settings1Tab($innerNotebook);
        $self->settings2Tab($innerNotebook);
        $self->settings3Tab($innerNotebook);
        $self->settings4Tab($innerNotebook);
        $self->settings5Tab($innerNotebook);
        $self->settings6Tab($innerNotebook);
        $self->settings7Tab($innerNotebook);
        $self->settings8Tab($innerNotebook);
        $self->settings9Tab($innerNotebook);
        $self->settings10Tab($innerNotebook);
        $self->settings11Tab($innerNotebook);
        $self->settings12Tab($innerNotebook);

        return 1;
    }

    sub settings1Tab {

        # Settings1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $gap,
            @comboList, @comboList2,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [
                'Connection details',
                'SSH Connection details',
                'Previous connections',
                'General settings',
            ],
        );

        # Connection details
        $self->addLabel($grid, '<b>Connection details</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'DNS',
            1, 3, 1, 2);
        $self->addEntryWithIcon($grid, 'dns', 'string', undef, undef,
            3, 6, 1, 2);
        $self->addLabel($grid, 'IPV4',
            1, 3, 2, 3);
        $self->addEntryWithIcon($grid, 'ipv4', \&ipv4Check, undef, undef,
            3, 6, 2, 3);
        $self->addLabel($grid, 'IPV6',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'ipv6', \&ipv6Check, undef, undef,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Port',
            1, 3, 4, 5);
        $self->addEntryWithIcon($grid, 'port', 'int', 0, 65535,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Protocol',
            1, 3, 5, 6);
        $self->addComboBox($grid, 'protocol', ['telnet', 'ssh', 'ssl'], '',
            TRUE,           # No 'undef' value used
            3, 6, 5, 6);

        if ($axmud::NO_SSL_FLAG) {

            $gap = 1;
            $self->addLabel($grid, 'NB <i>SSL connections are not available on this system</i>',
                1, 6, 6, 7);

        } else {

            $gap = 0;
        }

        # SSH Connection details
        $self->addLabel($grid, '<b>SSH Connection details</b>',
            0, 6, (6 + $gap), (7 + $gap));
        $self->addLabel($grid, 'SSH username',
            1, 3, (7 + $gap), (8 + $gap));
        $self->addEntryWithIcon($grid, 'sshUserName', 'string', undef, undef,
            3, 6, (7 + $gap), (8 + $gap));
        $self->addLabel($grid, 'SSH password',
            1, 3, (8 + $gap), (9 + $gap));
        $self->addEntryWithIcon($grid, 'sshPassword', 'string', undef, undef,
            3, 6, (8 + $gap), (9 + $gap));
        $self->addCheckButton($grid, 'Specify port in SSH connections', 'sshPortFlag', TRUE,
            1, 6, (9 + $gap), (10 + $gap));

        # Previous connections
        $self->addLabel($grid, '<b>Previous connections</b>',
            7, 12, 0, 1);
        $self->addLabel($grid, 'Last connect date',
            7, 9, 1, 2);
        $self->addEntry($grid, 'lastConnectDate', FALSE,
            9, 12, 1, 2, 20, 20);        # Max chars
        $self->addLabel($grid, 'Last connect time',
            7, 9, 2, 3);
        $self->addEntry($grid, 'lastConnectTime', FALSE,
            9, 12, 2, 3, 20, 20);        # Max chars
        $self->addLabel($grid, 'Last character used',
            7, 9, 3, 4);
        $self->addEntry($grid, 'lastConnectChar', FALSE,
            9, 12, 3, 4, 20, 20);        # Max chars
        $self->addLabel($grid, 'Total no. connections',
            7, 9, 4, 5);
        $self->addEntry($grid, 'numberConnects', FALSE,
            9, 12, 4, 5, 20, 20);        # Max chars

        # General settings
        $self->addLabel($grid, '<b>General settings</b>',
            7, 12, 5, 6);
        $self->addCheckButton($grid, 'Ever a current profile?', 'setupCompleteFlag', FALSE,
            7, 12, 6, 7);
        $self->addCheckButton(
            $grid, 'World can\'t be saved (during this session)', 'noSaveFlag', FALSE,
            7, 12, 7, 8);
        $self->addCheckButton($grid, 'Life status override', 'lifeStatusOverrideFlag', TRUE,
            7, 12, 8, 9);

        # Tab complete
        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $allString, $reverseFlag,
            @columnList, @charList, @comboList,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Connection history'],
        );

        # Connection history
        $self->addLabel($grid, '<b>Connection history</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid, '<i>Connection history for this world (updated only when the global flag is'
            . ' set. Estimated times in square brackets [...] )</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Character', 'text',
            'Attempted', 'text',
            'Current', 'bool',
            'Connected', 'text',
            'Disconnected', 'text',
            'Connection length', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list, which starts in reverse order
        $reverseFlag = TRUE;
        $self->settings2Tab_refreshList($slWidget, (scalar @columnList / 2), $reverseFlag);

        # Add editing buttons
        $self->addLabel($grid, 'Filter by character:',
            1, 2, 10, 11);

        foreach my $profObj ($self->session->ivValues('profHash')) {

            if ($profObj->category eq 'char') {

                push (@charList, $profObj->name);
            }
        }

        @comboList = sort {lc($a) cmp lc($b)} (@charList);
        $allString = 'Use all characters';
        unshift (@comboList, $allString);

        my $combo = $self->addComboBox($grid, undef, \@comboList, '', TRUE,
            2, 6, 10, 11);

        my $button = $self->addButton($grid,
            'Filter', 'Filter the list', undef,
            6, 7, 10, 11);
        $button->signal_connect('clicked' => sub {

            my $char = $combo->get_active_text();
            if ($char eq $allString) {

                # Refresh the full list
                $self->settings2Tab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    $reverseFlag,
                );

            } else {

                # Filter the list
                $self->settings2Tab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2), $reverseFlag,
                    $char,
                );
            }
        });

        my $button2 = $self->addButton($grid,
            'Reverse', 'Reverse the order of the list', undef,
            7, 8, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my $char;

            if (! $reverseFlag) {
                $reverseFlag = TRUE;
            } else {
                $reverseFlag = FALSE;
            }

            $char = $combo->get_active_text();
            if ($char eq $allString) {

                # Refresh the full list
                $self->settings2Tab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    $reverseFlag,
                );

            } else {

                # Filter the list
                $self->settings2Tab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2), $reverseFlag,
                    $char,
                );
            }
        });

        my $button3 = $self->addButton($grid,
            'Refresh', 'Refresh the list', undef,
            8, 10, 10, 11);
        $button3->signal_connect('clicked' => sub {

            my $char = $combo->get_active_text();
            if ($char eq $allString) {

                # Refresh the full list
                $self->settings2Tab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2),
                    $reverseFlag,
                );

            } else {

                # Filter the list
                $self->settings2Tab_refreshList(
                    $slWidget,
                    (scalar @columnList / 2), $reverseFlag,
                    $char,
                );
            }
        });

        my $button4 = $self->addButton($grid,
            'Clear', 'Clear the list', undef,
            10, 12, 10, 11);
        $button4->signal_connect('clicked' => sub {

            # Add an empty list to $self->editHash
            $self->ivAdd('editHash', 'connectHistoryList', []);

            # Refresh the simple list and reset entry boxes
            $self->settings2Tab_refreshList($slWidget, (scalar @columnList / 2), $reverseFlag);
            $combo->set_active(0);
        });

        # Tab complete
        return 1;
    }

    sub settings2Tab_refreshList {

        # Resets the simple list displayed by $self->settings2Tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns
        #   $reverseFlag    - If TRUE, the most recent connection is shown first. If FALSE, the
        #                       earliest connection is shown first
        #
        # Optional arguments
        #   $filter         - If set, filter the list showing only connections using the specified
        #                       character profile name. If 'undef', all connections are shown
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $reverseFlag, $filter, $check) = @_;

        # Local variables
        my (@dataList, @ivList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->settings2Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        @ivList = $self->getEditHash_listIV('connectHistoryList');
        if ($reverseFlag) {

            @ivList = reverse @ivList;
        }

        # Compile the simple list data
        foreach my $historyObj (@ivList) {

            my ($char, $cTime, $flag, $cdTime, $dcTime, $len);

            if (! $filter || ($historyObj->char && $historyObj->char eq $filter)) {

                if (! defined $historyObj->char) {
                    $char = '(not set)';
                } else {
                    $char = $historyObj->char;
                }

                if (defined $historyObj->connectingTime) {

                    $cTime = $axmud::CLIENT->convertTime($historyObj->connectingTime, 'date') . ', '
                                . $axmud::CLIENT->convertTime($historyObj->connectingTime, 'clock');
                }

                if (
                    $self->session->connectHistoryObj
                    && $self->session->connectHistoryObj eq $historyObj
                ) {
                    $flag = TRUE;
                } else {
                    $flag = FALSE;
                }

                if (defined $historyObj->connectedTime) {

                    $cdTime = $axmud::CLIENT->convertTime($historyObj->connectedTime, 'clock');
                }

                if (
                    ! $self->session->connectHistoryObj
                    || $self->session->connectHistoryObj ne $historyObj
                ) {
                    if (! defined $historyObj->disconnectedTime) {

                        # Axmud crashed or was terminated abruptly
                        $dcTime = '['
                            . $axmud::CLIENT->convertTime($historyObj->currentTime, 'clock') . ']';

                    } else {

                        $dcTime
                            = $axmud::CLIENT->convertTime($historyObj->disconnectedTime, 'clock');
                    }
                }

                if (defined $historyObj->connectionLength) {

                    if (
                        ! defined $historyObj->disconnectedTime
                        && (
                            ! $self->session->connectHistoryObj
                            || $self->session->connectHistoryObj ne $historyObj
                        )
                    ) {
                        $len = '[' . $axmud::CLIENT->getCounter($historyObj->connectionLength)
                                    . ']';

                    } else {

                        $len = $axmud::CLIENT->getCounter($historyObj->connectionLength);
                    }
                }

                push (@dataList, $char, $cTime, $flag, $cdTime, $dcTime, $len);
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub settings3Tab {

        # Settings3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Websites', 'World description'],
        );

        # Websites. ->signal_connects appear further below
        $self->addLabel($grid, '<b>Websites</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Website URL',
            1, 3, 1, 2);
        my $entry = $self->addEntry($grid, 'worldURL', TRUE,
            3, 10, 1, 2);
        my $button = $self->addButton($grid,
            'View', 'View this link', undef,
            10, 12, 1, 2);

        $self->addLabel($grid, 'Referrer URL',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($grid, 'referURL', TRUE,
            3, 10, 2, 3);
        my $button2 = $self->addButton($grid,
            'View', 'View this link', undef,
            10, 12, 2, 3);

        $button->signal_connect('clicked' => sub {

            # Let the user open junk URLs, if they want to, but don't let them open a non-existent
            #   URL
            my $url = $entry->get_text();
            if ($url) {

                $axmud::CLIENT->openURL($url);
            }
        });

        $button2->signal_connect('clicked' => sub {

            my $url = $entry2->get_text();
            if ($url) {

                $axmud::CLIENT->openURL($url);
            }
        });

        # World description
        $self->addLabel($grid, '<b>World description</b>',
            0, 3, 3, 4);
        $self->addLabel($grid, 'General description',
            1, 3, 4, 5);
        $self->addLabel($grid, '',         # Empty labels to get the spacing right
            1, 3, 5, 6);
        $self->addLabel($grid, '',
            1, 3, 6, 7);
        $self->addLabel($grid, '',
            1, 3, 7, 8);
        $self->addLabel($grid, '',
            1, 3, 8, 9);
        $self->addLabel($grid, '',
            1, 3, 9, 10);
        $self->addTextView($grid, 'worldDescrip', TRUE,
            3, 12, 4, 10,
            FALSE, undef, undef, TRUE,      # Treat as a scalar, not a list/forbid horizontal scroll
        );

        $self->addLabel($grid, 'World hint',
            1, 3, 10, 11);
        $self->addEntry($grid, 'worldHint', TRUE,
            3, 12, 10, 11);

        # Tab complete
        return 1;
    }

    sub settings4Tab {

        # Settings4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Automatic login mode (1/3)'],
        );

        # Automatic login mode (1/3)
        $self->addLabel($grid, '<b>Automatic login mode (1/3)</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'none\'', 'loginMode',
            'none',          # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel($grid, 'No automatic login (user must use \';login\' command)',
            4, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'immediate\'', 'loginMode', 'immediate', TRUE,
            1, 4, 2, 3);
        $self->addLabel($grid, 'Immediate login (not recommended)',
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'lp\'', 'loginMode', 'lp', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $grid, 'LP/Diku/AberMUD login (consecutive prompts for character/password)',
            4, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'tiny\'', 'loginMode', 'tiny', TRUE,
            1, 4, 4, 5);
        $self->addLabel(
            $grid, 'TinyMUD login (send \'connect char pass\' at first prompt)',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'world_cmd\'', 'loginMode', 'world_cmd', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $grid, 'Send a sequence of world commands at the first prompt (see next tab)',
            4, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'telnet\'', 'loginMode', 'telnet', TRUE,
            1, 4, 6, 7);
        $self->addLabel(
            $grid, 'Basic telnet login (e.g. \'login:\', \'password:\')',
            4, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'task\'', 'loginMode', 'task', TRUE,
            1, 4, 7, 8);
        $self->addLabel(
            $grid, 'Run a task to handle the login (see next tab)',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'script_task\'', 'loginMode', 'script_task', TRUE,
            1, 4, 8, 9);
        $self->addLabel(
            $grid,
            'Run ' . $axmud::BASIC_ARTICLE . ' script from within a task to handle the login (see'
            . ' next tab)',
            4, 12, 8, 9);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'script\'', 'loginMode', 'script', TRUE,
            1, 4, 9, 10);
        $self->addLabel(
            $grid, 'Run ' . $axmud::BASIC_ARTICLE . ' script to handle the login (see next tab)',
            4, 12, 9, 10);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'mission\'', 'loginMode', 'mission', TRUE,
            1, 4, 10, 11);
        $self->addLabel(
            $grid, 'Start a mission to handle the login (see next tab)',
            4, 12, 10, 11);

        # Tab complete
        return 1;
    }

    sub settings5Tab {

        # Settings5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Automatic login mode (2/3)'],
        );

        # Automatic login mode (2/3)
        $self->addLabel($grid, '<b>Automatic login mode (2/3)</b>',
            0, 10, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'loginConnectPatternList',
                'list', 'loginSuccessPatternList',
            ],
            10, 12, 0, 1);

        $self->addLabel(
            $grid, '<i>(NB The \'Initial\' tab lists the tasks, scripts and missions that start'
            . ' when the login process is complete)</i>',
            1, 12, 1, 2);
        $self->addLabel(
            $grid,
            '<i>(NB In mode \'world_cmd\', commands containing @name@, @account@ and/or'
            . ' @password@ are substituted)</i>',
            1, 12, 2, 3);

        $self->addLabel($grid, 'Mode \'tiny\': List of login expected patterns',
            1, 8, 3, 4);
        $self->addTextView($grid, 'loginConnectPatternList', TRUE,
            1, 8, 4, 6);

        $self->addLabel($grid, 'Mode \'world_cmd\': List of world commands to send',
            8, 12, 3, 4);
        $self->addTextView($grid, 'loginCmdList', TRUE,
            8, 12, 4, 6);

        $self->addLabel(
            $grid,
            'Mode \'lp\', \'tiny\', \'world_cmd\', \'telnet\': Success patterns',
            1, 8, 7, 8);
        $self->addTextView($grid, 'loginSuccessPatternList', TRUE,
            1, 8, 8, 12,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, 'Task/script/mission modes: task/script/mission to start',
            8, 12, 7, 8);
        my $entry = $self->addEntry($grid, 'loginObjName', TRUE,
            8, 12, 8, 9);

        # Add a button
        my $button = $self->addButton($grid,
            'View / Edit', 'View the task, or edit the script/mission', undef,
            10, 12, 9, 10);
        $button->signal_connect('clicked' => sub {

            my ($text, $mode);

            $text = $entry->get_text();
            $mode = $self->getEditHash_scalarIV('loginMode');

            # (Do nothing, if the entry box is empty)
            if ($text) {

                # Login mode 'task' - run a task (character is logged if the task calls
                #   GA::Session->doLogin)
                if ($mode eq 'task') {

                    # Open an 'edit' window for this task
                    $self->session->pseudoCmd('edittask ' . $text, $self->pseudoCmdMode);

                # Login mode 'script_task' - run an Axbasic script from within a task (character is
                #   logged in if the script executes a LOGIN statement)
                # Login mode 'script' - run an Axbasic script (character is logged in if the script
                #   executes a LOGIN statement)
                } elsif ($mode eq 'script_task' || $mode eq 'script') {

                    # Open the script in default text editor
                    $self->session->pseudoCmd('editscript ' . $text, $self->pseudoCmdMode);

                # Login mode 'mission' - start a mission (character is logged in if the mission uses
                #   the ';login' client command)
                } elsif ($mode eq 'mission') {

                    # Open an 'edit' window for this mission
                    $self->session->pseudoCmd('editmission ' . $text, $self->pseudoCmdMode);
                }
            }
        });

        $self->addLabel($grid, '',     # Get grid spacing right using empty labels
            8, 12, 10, 11);
        $self->addLabel($grid, '',     # Get grid spacing right using empty labels
            8, 12, 11, 12);

        # Tab complete
        return 1;
    }

    sub settings6Tab {

        # Settings6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Automatic login mode (3/3)'],
        );

        # Automatic login mode (3/3)
        $self->addLabel($grid, '<b>Automatic login mode (3/3)</b>',
            0, 12, 0, 1);

        $self->addLabel($grid,
            'Mode \'mission\': List of patterns matching lines with character names, and'
            . ' world command substitutions',
            1, 12, 1, 2);
        $self->addLabel($grid,
            '    <i>e.g. line  <b>1 Gandalf</b>  -  pattern'
            . '  <b>^(\d+)\s(\w+)$</b>  -  group substring number  <b>2</b>  -  command'
            . '  <b>$1</b></i>',
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
            'Substitution', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 9);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'loginSpecialList');

        # Add entries for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 12, 9, 10);

        $self->addLabel($grid, 'Substring #:',
            1, 3, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($grid, 'World command:',
            6, 8, 10, 11);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            8, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'loginSpecialList',
            11, 3,
            $entry, $entry2, $entry3,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $cmd);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $cmd = $entry3->get_text();

            if ($self->checkEntryIcon($entry, $entry2, $entry3)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'loginSpecialList',
                    undef, FALSE,
                    $pattern, $grpNum, $cmd,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'loginSpecialList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub settings7Tab {

        # Settings7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings7Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Automatic quit mode'],
        );

        # Automatic quit mode
        $self->addLabel($grid, '<b>Automatic quit mode</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>What happens when you use the client commands \';quit\' , \';qquit\' or'
            . ' \';quitall\'</i>',
            1, 12, 1, 2);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'normal\'', 'autoQuitMode',
            'normal',   # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 2, 3);
        $self->addLabel(
            $grid,
            'Send standard \'quit\' command',
            3, 6, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'world_cmd\'', 'autoQuitMode', 'world_cmd', TRUE,
            7, 9, 2, 3);
        $self->addLabel($grid, 'Send a sequence of commands',
            9, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'task\'', 'autoQuitMode', 'task', TRUE,
            1, 3, 3, 4);
        $self->addLabel(
            $grid, 'Run a task',
            3, 6, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'task_script\'', 'autoQuitMode', 'task_script', TRUE,
            7, 9, 3, 4);
        $self->addLabel(
            $grid, 'Run ' . $axmud::BASIC_ARTICLE . ' script as a task',
            9, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'script\'', 'autoQuitMode', 'script', TRUE,
            1, 3, 4, 5);
        $self->addLabel(
            $grid, 'Run ' . $axmud::BASIC_ARTICLE . ' script',
            3, 6, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'mission\'', 'autoQuitMode', 'mission', TRUE,
            7, 9, 4, 5);
        $self->addLabel(
            $grid, 'Start a mission',
            9, 12, 4, 5);

        $self->addLabel($grid, 'Mode \'world_cmd\': List of world commands to send',
            1, 6, 5, 6);
        $self->addTextView($grid, 'autoQuitCmdList', TRUE,
            # (Use an abnormally large grid to get the widget spacing right)
            1, 6, 6, 24);

        $self->addLabel($grid, 'Other modes: task, script or mission to start',
            7, 12, 5, 6);
        my $entry = $self->addEntry($grid, 'autoQuitObjName', TRUE,
            7, 12, 6, 7);

        # Add a button
        my $button = $self->addButton($grid,
            'View / Edit', 'View the task, or edit the script/mission', undef,
            10, 12, 7, 8);
        $button->signal_connect('clicked' => sub {

            my ($text, $mode);

            $text = $entry->get_text();
            $mode = $self->getEditHash_scalarIV('autoQuitMode');

            # (Do nothing, if the entry box is empty)
            if ($text) {

                # Auto-quit mode 'task' - run a task (the task is responsible for sending a 'quit'
                #   world command)
                if ($mode eq 'task') {

                    # Open an 'edit' window for this task
                    $self->session->pseudoCmd('edittask ' . $text, $self->pseudoCmdMode);

                # Auto-quit mode 'task_script' - run an Axbasic script from within a task (the
                #   script is responsible for sending a 'quit' world command)
                # Auto-quit mode 'script' - run an Axbasic script  (the script is responsible for
                #   sending a 'quit' world command)
                } elsif ($mode eq 'task_script' || $mode eq 'script') {

                    # Open the script in default text editor
                    $self->session->pseudoCmd('editscript ' . $text, $self->pseudoCmdMode);

                # Auto-quit mode 'mission' - start a mission (the mission is responsible for sending
                #   a 'quit' world command)
                } elsif ($mode eq 'mission') {

                    # Open an 'edit' window for this mission
                    $self->session->pseudoCmd('editmission ' . $text, $self->pseudoCmdMode);
                }
            }
        });

        # Tab complete
        return 1;
    }

    sub settings8Tab {

        # Settings8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $obscureFlag,
            @columnList,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings8Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Character/password list'],
        );

        # Character/password list
        $self->addLabel($grid, '<b>Character/password list</b>',
            0, 6, 0, 1);

        $self->addLabel($grid,
            '<i>(NB Setting the account login mode on the right is optional; it doesn\'t affect the'
            . ' automatic login process)</i>',
            1, 12, 1, 2);

        my $checkButton = $self->addCheckButton($grid, 'Obscure passwords', undef, TRUE,
            1, 4, 2, 3);
        # Passwords are initially obscured
        $obscureFlag = TRUE;
        $checkButton->set_active($obscureFlag);
        # (->signal_connect appears below)

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'unknown\'', 'loginAccountMode',
            'unknown',  # IV set to this value when toggled
            TRUE,       # Sensitive widget
            4, 5, 2, 3);
        $self->addLabel($grid, 'Unknown if account name is also required to log in',
            5, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'not_required\'', 'loginAccountMode', 'not_required', TRUE,
            4, 5, 3, 4);
        $self->addLabel($grid, 'Account name not required',
            5, 12, 3, 4);

         ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'required\'', 'loginAccountMode', 'required', TRUE,
            4, 5, 4, 5);
        $self->addLabel($grid, 'Account name required',
            5, 12, 4, 5);

        # Add a simple list
        @columnList = (
            'Character', 'text',
            'Password', 'text',
            'Associated account name (if any)', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 5, 11);

        # Initialise the list
        $self->settings8Tab_refreshList($slWidget, (scalar @columnList / 2), $obscureFlag);

        # ->signal_connect from above
        $checkButton->signal_connect('toggled' => sub {

            $obscureFlag = $checkButton->get_active();
            $self->settings8Tab_refreshList($slWidget, (scalar @columnList / 2), $obscureFlag);
        });

        # Add editing buttons
        my $button = $self->addButton($grid,
            'Edit password', 'Set or edit the password for the selected character', undef,
            1, 4, 11, 12);
        $button->signal_connect('clicked' => sub {

            my (
                $name, $pwd, $newPwd, $profObj,
                %passwordHash,
            );

            ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                # Import the world's password hash
                %passwordHash = $self->getEditHash_hashIV('passwordHash');

                # (Protect against missing character profiles...)
                $profObj = $self->session->ivShow('profHash', $name);
                if (
                    exists $passwordHash{$name}
                    && $profObj
                    && $profObj->category eq 'char'
                ) {
                    # Get the corresponding password (if any)
                    $pwd = $passwordHash{$name};

                    # Prompt the user for a new password
                    $newPwd = $self->showEntryDialogue(
                        'Set password',
                        'Set the password for \'' . $name . '\'',
                        undef,              # No max chars
                        $pwd,               # Current password
                        TRUE,               # Obscure text in the entry box
                    );

                    # (If user clicked 'cancel' button, $newPwd is 'undef'; if the user entered no
                    #   password, $newPwd is an empty string)
                    if (defined $newPwd) {

                        if ($newPwd) {
                            $passwordHash{$name} = $newPwd;
                        } else {
                            $passwordHash{$name} = undef;
                        }

                        # Update the IV
                        $self->ivPoke('editHash', 'passwordHash', \%passwordHash);
                    }
                }
            }

            # Refresh the simple list
            $self->settings8Tab_refreshList($slWidget, (scalar @columnList / 2), $obscureFlag);
        });

        my $button2 = $self->addButton($grid,
            'Edit account name', 'Set or edit the account name for the selected character', undef,
            4, 6, 11, 12);
        $button2->signal_connect('clicked' => sub {

            my (
                $name, $profObj, $newAccount,
                %accountHash,
            );

            ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                # Import the world's account hash
                %accountHash = $self->getEditHash_hashIV('accountHash');

                # (Protect against missing character profiles...)
                $profObj = $self->session->ivShow('profHash', $name);
                if (
                    exists $accountHash{$name}
                    && $profObj
                    && $profObj->category eq 'char'
                ) {
                    # Prompt the user for a new account name
                    $newAccount = $self->showEntryDialogue(
                        'Set account name',
                        'Set the associated account name for \'' . $name . '\'',
                        undef,                  # No max chars
                        $accountHash{$name},    # Current account name - may be 'undef'
                    );

                    # (If user clicked 'cancel' button, $newAccount is 'undef'; if the user entered
                    #   no account name, $newAccount is an empty string)
                    if (defined $newAccount) {

                        if ($newAccount) {
                            $accountHash{$name} = $newAccount;
                        } else {
                            $accountHash{$name} = undef;
                        }

                        # Update the IV
                        $self->ivPoke('editHash', 'accountHash', \%accountHash);
                    }
                }
            }

            # Refresh the simple list
            $self->settings8Tab_refreshList($slWidget, (scalar @columnList / 2), $obscureFlag);
        });

        my $button3 = $self->addButton($grid,
            'Refresh list', 'Refresh the list of characters', undef,
            9, 12, 11, 12);
        $button3->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->settings8Tab_refreshList($slWidget, (scalar @columnList / 2), $obscureFlag);
        });

        # Tab complete
        return 1;
    }

    sub settings8Tab_refreshList {

        # Resets the simple list displayed by $self->settings8Tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns
        #   $obscureFlag    - Flag set to TRUE if passwords should be obscured, FALSE otherwise
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $obscureFlag, $check) = @_;

        # Local variables
        my (
            @charList, @dataList,
            %passwordHash, %accountHash,
        );

        # Check for improper arguments
        if (
            ! defined $slWidget || ! defined $columns || ! defined $obscureFlag
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->settings8Tab_refreshList',
                @_,
            );
        }

        # Import the IVs
        %passwordHash = $self->getEditHash_hashIV('passwordHash');
        %accountHash = $self->getEditHash_hashIV('accountHash');
        # Get a sorted list of character names
        @charList = sort {lc($a) cmp lc($b)} (keys %passwordHash);

        # Compile the simple list data
        foreach my $charName (@charList) {

            my $password;

            if ($obscureFlag) {

                if ($passwordHash{$charName}) {
                    $password = '********';
                } else {
                    $password = '';
                }

            } else {

                $password = $passwordHash{$charName};
            }

            push (@dataList, $charName, $password, $accountHash{$charName});
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub settings9Tab {

        # Settings9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings9Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _9',
            ['Associated profiles'],
        );

        # Associated profiles
        $self->addLabel($grid, '<b>Associated profiles</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of profiles associated with this one (inclusive)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Name', 'text',
            'Category', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'profHash');

        # Add a button
        my $button = $self->addButton($grid,
            'Refresh list', 'Refresh the list of associated profiles', undef,
            9, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'profHash');
        });

        # Tab complete
        return 1;
    }

    sub settings10Tab {

        # Settings10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $noUndefFlag,
            @comboList,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings10Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 1_0',
            [
                'Dictionary settings',
                'Multiples',
                'Consecutive empty line suppression',
                'Command separator \';\'',
                'Slowwalking',
            ],
        );

        # Dictionary settings
        $self->addLabel($grid, '<b>Dictionary settings</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, '<i>Dictionary used with this world</i>',
            1, 6, 1, 2);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('dictHash'));

        # World profiles which have never been a current profile can have their ->dict IV set to
        #   'undef'; we need to take account of that when calling ->addComboBox
        if (defined $self->editObj->dict) {
            $noUndefFlag = TRUE;        # Combo doesn't contain an item corresponding to 'undef'
        } else {
            $noUndefFlag = FALSE;       # Combo does contain an item corresponding to 'undef'
        }

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            $noUndefFlag,
            1, 4, 2, 3);
        $self->settings10Tab_refreshCombo($combo);

        my $button = $self->addButton($grid, 'Edit...', 'Edit the selected dictionary', undef,
            4, 6, 2, 3);
        $button->signal_connect('clicked' => sub {

            my ($dictName, $dictObj, $childWinObj);

            # Get the dictionary name selected in the combo
            $dictName = $combo->get_active_text();
            if ($dictName) {

                # Get the blessed reference
                $dictObj = $axmud::CLIENT->ivShow('dictHash', $dictName);
            }

            if ($dictObj) {

                # Open up a dictionary 'edit' window to edit the existing dictionary
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::Dict',
                    $self,
                    $self->session,
                    'Edit dictionary \'' . $dictName . '\'',
                    $dictObj,
                    FALSE,                  # Not temporary
                );
            }

            if ($childWinObj) {

                # When the 'edit' window closes, update widgets and/or IVs
                $self->add_childDestroy(
                    $childWinObj,
                    'settings10Tab_refreshCombo',
                    [$combo],
                );
            }
        });

        my $button2 = $self->addButton($grid, 'New...', 'Add new dictionary', undef,
            1, 4, 3, 4,
            TRUE);          # Irreversible
        $button2->signal_connect('clicked' => sub {

            my ($dictObj, $childWinObj);

            # Create a temporary dictionary
            $dictObj = Games::Axmud::Obj::Dict->new($self->session, '<temp>', 'English', TRUE);
            if ($dictObj) {

                # Open up a dictionary 'edit' window to create the new dictionary, replacing the
                #   temporary one we've just created
                $self->createFreeWin(
                    'Games::Axmud::EditWin::Dict',
                    $self,
                    $self->session,
                    'Edit dictionary \'' . $dictObj->name . '\'',
                    $dictObj,
                    TRUE,           # Temporary object
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'settings10Tab_refreshCombo',
                        [$combo],
                    );
                }
            }
        });

        my $button3 = $self->addButton(
            $grid, 'Refresh list', 'Refresh the list of dictionaries', undef,
            4, 6, 3, 4);
        $button3->signal_connect('clicked' => sub {

            # Refresh the combo
            $self->settings10Tab_refreshCombo($combo);
        });

        # Multiples
        $self->addLabel($grid, '<b>Multiples</b>',
            0, 12, 4, 5);
        $self->addCheckButton($grid, 'Add numbers to similar objects', 'numberedObjFlag', TRUE,
            1, 6, 5, 6);
        $self->addLabel($grid, 'Multiple object pattern',
            1, 4, 6, 7);
        $self->addEntryWithIconButton($grid, 'multiplePattern', 'regex', undef, undef,
            4, 6, 6, 7);

        # Consecutive empty line suppression
        $self->addLabel($grid, '<b>Consecutive empty line suppression</b>',
            7, 13, 0, 1);
        $self->addLabel($grid, 'Lines to suppress (0 - none, 1 - all)',
            8, 11, 1, 2);
        $self->addEntryWithIcon($grid, 'suppressEmptyLineCount', 'int', 0, undef,
            11, 13, 1, 2, 8, 8);
        $self->addCheckButton(
            $grid, 'Suppress empty lines before login', 'suppressBeforeLoginFlag', TRUE,
            8, 13, 2, 3);

        # Command separator
        $self->addLabel($grid, '<b>Command separator \'' . $axmud::CLIENT->cmdSep . '\'</b>',
            7, 12, 3, 4);
        $self->addCheckButton(
            $grid, 'Auto-separate commands in this world', 'autoSeparateCmdFlag', TRUE,
            8, 13, 4, 5);

        # Slowwalking
        $self->addLabel($grid, '<b>Slowwalking</b>',
            7, 13, 5, 6);
        $self->addLabel($grid, 'Max world commands (0 - no limit)',
            8, 11, 6, 7);
        $self->addEntryWithIcon($grid, 'excessCmdLimit', 'int', 0, undef,
            11, 13, 6, 7, 6, 4);
        $self->addLabel($grid, '...in time period (min 0.1 secs)',
            8, 11, 7, 8);
        $self->addEntryWithIcon($grid, 'excessCmdDelay', 'float', 0.1, undef,
            11, 13, 7, 8, 6, 4);

        # Tab complete
        return 1;
    }

    sub settings10Tab_refreshCombo {

        # Resets the combobox displayed by $self->settings10Tab
        #
        # Expected arguments
        #   $combo          - The Gtk3::Combo
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $combo, $check) = @_;

        # Local variables
        my (
            $currentDict,
            @dictList, @comboList,
        );

        # Check for improper arguments
        if (! defined $combo || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->settings10Tab_refreshCombo',
                @_,
            );
        }

        # Import the GA::Client's list of dictionaries, and sort them
        @dictList = sort {lc($a->name) cmp lc($b->name)} ($axmud::CLIENT->ivValues('dictHash'));

        # Get the profile's current dictionary (might be stored in $self->editHash, or the
        #   unmodified value stored in $self->editObj)
        $currentDict = $self->getEditHash_scalarIV('dict');

        # If $currentDict is defined (and it should be), remove it from @dictList, and insert it at
        #   the beginning, so it can appear at the top of the combo
        foreach my $dictObj (@dictList) {

            if (! $currentDict || $currentDict ne $dictObj->name) {

                push (@comboList, $dictObj->name);
            }
        }

        if ($currentDict) {

            unshift (@comboList, $currentDict);

        } else {

            # (If $currentDict was not, for some reason, defined, an empty string is inserted for
            #   setting the IV to 'undef')
            unshift (@comboList, '');
        }

        # Refill the combo
        $self->resetComboBox($combo, @comboList);

        # Operation complete
        return 1;
    }

    sub settings11Tab {

        # Settings11 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings11Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 11',
            ['Units', 'Command prompt patterns'],
        );

        # Units
        $self->addLabel($grid, '<b>Units</b>',
            0, 10, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'cmdPromptPatternList',
            ],
            10, 12, 0, 1);

        $self->addLabel($grid, 'Character age units',
            1, 6, 1, 2);
        $self->addEntryWithButton($grid, 'charAgeUnit', TRUE,
            1, 6, 2, 3);
        $self->addLabel($grid, 'Standard weight unit',
            1, 6, 3, 4);
        $self->addEntryWithButton($grid, 'weightUnit', TRUE,
            1, 6, 4, 5);
        $self->addLabel($grid, 'Strings for weight units (inc. plurals)',
            7, 12, 1, 2);
        $self->addTextView($grid, 'weightUnitStringList', TRUE,
            7, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
        );

        # Command prompt patterns
        $self->addLabel(
            $grid,
            '<b>Command prompt patterns</b>',
            0, 5, 6, 7);              # Use 5, rather than 12, to avoid Gtk layout problems
        $self->addLabel(
            $grid,
#            '<i>You can add an initial ^ character, but do not add a terminating $ character</i>',
            '<i>Do not add a terminating $ character</i>',
            1, 12, 7, 8);
        $self->addTextView($grid, 'cmdPromptPatternList', TRUE,
            1, 12, 8, 12,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub settings12Tab {

        # Settings12 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings12Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 12',
            ['Session logging settings'],
        );

        # Session logging settings
        $self->addLabel($grid, '<b>Session logging settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of logfiles written by each session using this world profile</i>',
            1, 12, 1, 2);

        # Add a simple list. Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            @columnList = (
                'Logfile', 'text',
                'Write?', 'bool',
                'Description', 'text',
            );

        } else {

            @columnList = (
                'Logfile', 'text',
                'Write?', 'bool_editable',
                'Description', 'text',
            );
        }

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 12,
            -1, -1,
            $self->getMethodRef('settings12Tab_buttonToggled'),
        );

        # Initialise the list
        $self->settings12Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Tab complete
        return 1;
    }

    sub settings12Tab_refreshList {

        # Resets the simple list displayed by $self->settings12Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->settings12Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('logPrefHash');

        # Compile the simple list data
        foreach my $logfile ($axmud::CLIENT->constSessionLogOrderList) {

            push (@dataList,
                $logfile,
                $ivHash{$logfile},
                $axmud::CLIENT->ivShow('constLogDescripHash', $logfile),
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub settings12Tab_buttonToggled {

        # Callback from GA::Generic::EditWin->addSimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $model      - The Gtk3::ListStore
        #   $iter       - The Gtk3::TreeIter of the line clicked
        #   @data       - Data for all the cells on that line
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $model, $iter, @dataList) = @_;

        # Local variables
        my ($logfile, $flag);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $model || ! defined $iter) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->settings12Tab_buttonToggled',
                @_,
            );
        }

        # Update IVs
        $logfile = $dataList[0];
        $flag = $dataList[1];

        if (! $self->editObj->ivExists('logPrefHash', $logfile)) {

            # Failsafe
            return undef;
        }

        if (
            ($self->editObj->ivShow('logPrefHash', $logfile) && ! $flag)
            || (! $self->editObj->ivShow('logPrefHash', $logfile) && $flag)
        ) {
            $self->session->pseudoCmd('log ' . $logfile, $self->pseudoCmdMode);
        }

        return 1;
    }

    sub overrideTab {

        # Override tab
        #
        # 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 . '->overrideTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Overri_de');

        # Add tabs to the inner notebook
        $self->override1Tab($innerNotebook);
        $self->override2Tab($innerNotebook);
        $self->override3Tab($innerNotebook);
        $self->override4Tab($innerNotebook);
        $self->override5Tab($innerNotebook);

        return 1;
    }

    sub override1Tab {

        # Override1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $defaultTtype, $currentTtype, $defaultCharSet, $currentCharSet,
            @comboList, @comboList2,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->override1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [
                'Override ' . $axmud::SCRIPT . ' terminal type',
                'Override ' . $axmud::SCRIPT . ' character set',
                'Override real terminal size (disables NAWS)'
            ],
        );

        # Left column
        $self->addLabel($grid, '<b>Override ' . $axmud::SCRIPT . ' terminal type</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Own termtype',
            1, 3, 1, 2);

        $defaultTtype = '<default terminal type>';
        $currentTtype = $self->editObj->termType;
        if (defined $currentTtype) {

            push (@comboList, $currentTtype, $defaultTtype);
            foreach my $item ($axmud::CLIENT->constTermTypeList) {

                if ($item ne $currentTtype) {

                    push (@comboList, $item);
                }
            }

        } else {

            push (@comboList, $defaultTtype, $axmud::CLIENT->constTermTypeList);
        }

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # 'undef' value not allowed
            3, 6, 1, 2);
        $comboBox->signal_connect('changed' => sub {

            my $text = $comboBox->get_active_text();

            if ($text eq $defaultTtype) {

                $self->ivAdd('editHash', 'termType', undef);

            } else {

                $self->ivAdd('editHash', 'termType', $text);
            }
        });

        $self->addLabel($grid, '<b>Override ' . $axmud::SCRIPT . ' character set</b>',
            0, 6, 2, 3);
        $self->addLabel($grid, 'Own charset',
            1, 3, 3, 4);

        $defaultCharSet = '<default character set>';
        $currentCharSet = $self->editObj->worldCharSet;
        if (defined $currentCharSet) {

            push (@comboList2, $currentCharSet, $defaultCharSet);
            foreach my $item ($axmud::CLIENT->charSetList) {

                if ($item ne $currentCharSet) {

                    push (@comboList2, $item);
                }
            }

        } else {

            push (@comboList2, $defaultCharSet, $axmud::CLIENT->charSetList);
        }

        my $comboBox2 = $self->addComboBox($grid, undef, \@comboList2, '',
            TRUE,              # 'undef' value not allowed
            3, 6, 3, 4);
        $comboBox2->signal_connect('changed' => sub {

            my $text = $comboBox2->get_active_text();

            if ($text eq $defaultCharSet) {

                $self->ivAdd('editHash', 'worldCharSet', undef);

            } else {

                $self->ivAdd('editHash', 'worldCharSet', $text);
            }
        });

        # Right column
        $self->addLabel($grid, '<b>Override real terminal size (disables NAWS)</b>',
            7, 13, 0, 1);
        $self->addLabel($grid, 'Columns',
            8, 10, 1, 2);
        $self->addEntryWithIcon($grid, 'columns', 'int', 0, undef,
            10, 13, 1, 2);
        $self->addLabel($grid, 'Rows',
            8, 10, 2, 3);
        $self->addEntryWithIcon($grid, 'rows', 'int', 0, undef,
            10, 13, 2, 3);
        $self->addCheckButton($grid, 'Send size after login', 'sendSizeInfoFlag', TRUE,
            8, 13, 3, 4);

        # Tab complete
        return 1;
    }

    sub override2Tab {

        # Override2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->override2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Telnet override settings'],
        );

        # Telnet override settings
        $self->addLabel($grid, '<b>Telnet override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of telnet options/protocols which must not be enabled in a session when'
            . ' this is the current world</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Option/protocol', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 9);

        # Initialise the list
        $self->override2Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Option/protocol:',
            1, 3, 9, 10);

        @comboList = qw(
            echo sga ttype eor naws new_environ charset
            msdp mssp mccp msp mxp pueblo zmp aard102 atcp gmcp mtts mnes mcp
        );

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 5, 9, 10);

        my $button = $self->addButton(
            $grid,
            'Disable for this world',
            'Stop this option/protocol from being enabled in this world\'s session',
            undef,
            5, 12, 9, 10);
        $button->signal_connect('clicked' => sub {

            my (
                $string,
                %hash,
            );

            $string = $comboBox->get_active_text();
            %hash = $self->getEditHash_hashIV('telnetOverrideHash');
            $hash{$string} = undef;
            $self->ivAdd('editHash', 'telnetOverrideHash', \%hash);

            # Refresh the simple list
            $self->override2Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button2 = $self->addButton(
            $grid,
            'Enable/disable according to global settings',
            'Enable/disable this option/protocol according to global settings',
            undef,
            5, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my (
                $string,
                %hash,
            );

            $string = $comboBox->get_active_text();
            %hash = $self->getEditHash_hashIV('telnetOverrideHash');
            delete $hash{$string};
            $self->ivAdd('editHash', 'telnetOverrideHash', \%hash);

            # Refresh the simple list
            $self->override2Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button3 = $self->addButton(
            $grid,
            'Reset',
            'Reset the list of override settings',
            undef,
            8, 10, 11, 12);
        $button3->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'telnetOverrideHash');

            # Refresh the simple list
            $self->override2Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button4 = $self->addButton(
            $grid,
            'Clear',
            'Clear the list of override settings',
            undef,
            10, 12, 11, 12);
        $button4->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'telnetOverrideHash', {});

            # Refresh the simple list
            $self->override2Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub override2Tab_refreshList {

        # Resets the simple list displayed by $self->override2Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->override2Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('telnetOverrideHash');

        # Compile the simple list data
        @dataList = sort {$a cmp $b} (keys %ivHash);

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub override3Tab {

        # Override3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            @columnList, @setupList, @comboList,
            %comboHash,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->override3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['MXP override settings'],
        );

        # MXP override settings
        $self->addLabel($grid, '<b>MXP override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of MXP settings which should be enabled/disabled when this is the current'
            . ' world, overriding global settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Setting type', 'text',
            'Enabled', 'bool',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 10);

        # Initialise the list
        $self->override3Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Setting type:',
            1, 3, 10, 11);

        @setupList = (
            'room'      => 'Use MXP room data',
            'flexible'  => 'Allow (some) illegal MXP keywords',
            'perm'      => 'Assume world has enabled MXP',
        );

        do {

            my ($type, $descrip);

            $type = shift @setupList;
            $descrip = shift @setupList;

            push (@comboList, $descrip);
            $comboHash{$descrip} = $type;

        } until (! @setupList);

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 6, 10, 11);

        my $button = $self->addButton(
            $grid,
            'Enable for this world',
            'Enable this setting in this world\'s sessions',
            undef,
            6, 9, 10, 11);
        $button->signal_connect('clicked' => sub {

            my (
                $type,
                %hash,
            );

            $type = $comboHash{$comboBox->get_active_text()};
            %hash = $self->getEditHash_hashIV('mxpOverrideHash');
            $hash{$type} = TRUE;
            $self->ivAdd('editHash', 'mxpOverrideHash', \%hash);

            # Refresh the simple list
            $self->override3Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button2 = $self->addButton(
            $grid,
            'Disable for this world',
            'Disable this setting in this world\'s sessions',
            undef,
            9, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my (
                $type,
                %hash,
            );

            $type = $comboHash{$comboBox->get_active_text()};
            %hash = $self->getEditHash_hashIV('mxpOverrideHash');
            $hash{$type} = FALSE;
            $self->ivAdd('editHash', 'mxpOverrideHash', \%hash);

            # Refresh the simple list
            $self->override3Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button3 = $self->addButton(
            $grid,
            'Use global settings',
            'Enable/disable this setting according to global settings',
            undef,
            6, 8, 11, 12);
        $button3->signal_connect('clicked' => sub {

            my (
                $type,
                %hash,
            );

            $type = $comboHash{$comboBox->get_active_text()};
            %hash = $self->getEditHash_hashIV('mxpOverrideHash');
            delete $hash{$type};
            $self->ivAdd('editHash', 'mxpOverrideHash', \%hash);

            # Refresh the simple list
            $self->override3Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button4 = $self->addButton(
            $grid,
            'Reset',
            'Reset the list of override settings',
            undef,
            8, 10, 11, 12);
        $button4->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'mxpOverrideHash');

            # Refresh the simple list
            $self->override3Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button5 = $self->addButton(
            $grid,
            'Clear',
            'Clear the list of override settings',
            undef,
            10, 12, 11, 12);
        $button5->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'mxpOverrideHash', {});

            # Refresh the simple list
            $self->override3Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub override3Tab_refreshList {

        # Resets the simple list displayed by $self->override3Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->override3Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('mxpOverrideHash');

        # Compile the simple list data
        foreach my $type ('room', 'flexible', 'perm') {

            my $iv;

            if (exists $ivHash{$type}) {

                push (@dataList, $type, $ivHash{$type});
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub override4Tab {

        # Override4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            @columnList, @initList, @comboList, @initList2, @comboList2, @comboList3,
            %comboHash, %comboHash2,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->override4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Terminal type override settings'],
        );

        # Terminal type override settings
        $self->addLabel($grid, '<b>Terminal type override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of terminal type override settings which must be applied when this is the'
            . ' current world, overriding global settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Setting', 'text',
            'Value (1 - enabled)', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 7);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');

        # Add editing widgets
        $self->addLabel($grid, 'TTYPE negotiations',
            1, 3, 7, 8);

        @initList = (
            'send_nothing'          => 'Send nothing',
            'send_client'           => 'Send client name, then usual list',
            'send_client_version'   => 'Send client/name version, then usual list',
            'send_custom_client'    => 'Send custom client/name version, then usual list',
            'send_default'          => 'Send usual terminal type list',
            'send_unknown'          => 'Send \'unknown\'',
        );

        do {

            my ($mode, $descrip);

            $mode = shift @initList;
            $descrip = shift @initList;

            push (@comboList, $descrip);
            $comboHash{$descrip} = $mode;

        } until (! @initList);

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 8, 7, 8);

        my $button = $self->addButton($grid, 'Use', 'Use this setting in the session', undef,
            8, 10, 7, 8);
        $button->signal_connect('clicked' => sub {

            my (
                $mode,
                %hash,
            );

            $mode = $comboHash{$combo->get_active_text()};
            %hash = $self->getEditHash_hashIV('termOverrideHash');
            $hash{termTypeMode} = $mode;
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the combobox
            $combo->set_active(0);
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        my $button2 = $self->addButton(
            $grid,
            'Don\'t use',
            'Don\'t use this setting in the session',
            undef,
            10, 12, 7, 8);
        $button2->signal_connect('clicked' => sub {

            my %hash = $self->getEditHash_hashIV('termOverrideHash');
            delete $hash{'termTypeMode'};
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the combobox
            $combo->set_active(0);
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        $self->addLabel($grid, 'Custom client name',
            1, 3, 8, 9);
        my $entry = $self->addEntry($grid, undef, TRUE,
            3, 8, 8, 9);

        my $button3 = $self->addButton($grid, 'Use', 'Use this setting in the session', undef,
            8, 10, 8, 9);
        $button3->signal_connect('clicked' => sub {

            my (
                $string,
                %hash,
            );

            $string = $entry->get_text();
            if (! defined $string) {

                $string = '';
            }

            %hash = $self->getEditHash_hashIV('termOverrideHash');
            $hash{'customClientName'} = $string;
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the entry box
            $entry->set_text('');
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        my $button4 = $self->addButton(
            $grid,
            'Don\'t use',
            'Don\'t use this setting in the session',
            undef,
            10, 12, 8, 9);
        $button4->signal_connect('clicked' => sub {

            my %hash = $self->getEditHash_hashIV('termOverrideHash');
            delete $hash{'customClientName'};
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the entry box
            $entry->set_text('');
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        $self->addLabel($grid, 'Custom client version',
            1, 3, 9, 10);
        my $entry2 = $self->addEntry($grid, undef, TRUE,
            3, 8, 9, 10);

        my $button5 = $self->addButton($grid, 'Use', 'Use this setting in the session', undef,
            8, 10, 9, 10);
        $button5->signal_connect('clicked' => sub {

            my (
                $string,
                %hash,
            );

            $string = $entry2->get_text();
            if (! defined $string) {

                $string = '';
            }

            %hash = $self->getEditHash_hashIV('termOverrideHash');
            $hash{'customClientVersion'} = $string;
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the entry box
            $entry2->set_text('');
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        my $button6 = $self->addButton(
            $grid,
            'Don\'t use',
            'Don\'t use this setting in the session',
            undef,
            10, 12, 9, 10);
        $button6->signal_connect('clicked' => sub {

            my %hash = $self->getEditHash_hashIV('termOverrideHash');
            delete $hash{'customClientVersion'};
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the entry box
            $entry2->set_text('');
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        @initList2 = (
            'useCtrlSeqFlag'          => 'Use VT100 control sequences',
            'useVisibleCursorFlag'    => 'Show visible cursor in default textview',
            'useDirectKeysFlag'       => 'Use direct keyboard input in terminal',
        );

        do {

            my ($iv, $descrip);

            $iv = shift @initList2;
            $descrip = shift @initList2;

            push (@comboList2, $descrip);
            $comboHash2{$descrip} = $iv;

        } until (! @initList2);

        $self->addLabel($grid, 'Terminal emulation',
            1, 3, 10, 11);
        my $combo2 = $self->addComboBox($grid, undef, \@comboList2, '',
            TRUE,                       # No 'undef' value used
            3, 6, 10, 11);

        @comboList3 = ('Enabled', 'Disabled');
        my $combo3 = $self->addComboBox($grid, undef, \@comboList3, '',
            TRUE,                       # No 'undef' value used
            6, 8, 10, 11);

        my $button7 = $self->addButton($grid, 'Use', 'Use this setting in the session', undef,
            8, 10, 10, 11);
        $button7->signal_connect('clicked' => sub {

            my (
                $iv, $mode, $flag,
                %hash,
            );

            $iv = $comboHash2{$combo2->get_active_text()};
            $mode = $combo3->get_active_text();
            if ($mode eq 'Enabled') {
                $flag = TRUE;
            } else {
                $flag = FALSE;
            }

            %hash = $self->getEditHash_hashIV('termOverrideHash');
            $hash{$iv} = $flag;
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the comboboxes
            $combo2->set_active(0);
            $combo3->set_active(0);
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        my $button8 = $self->addButton(
            $grid,
            'Don\'t use',
            'Don\'t use this setting in the session',
            undef,
            10, 12, 10, 11);
        $button8->signal_connect('clicked' => sub {

            my (
                $iv,
                %hash,
            );

            $iv = $comboHash2{$combo2->get_active_text()};

            %hash = $self->getEditHash_hashIV('termOverrideHash');
            delete $hash{$iv};
            $self->ivAdd('editHash', 'termOverrideHash', \%hash);

            # Reset the comboboxes
            $combo2->set_active(0);
            $combo3->set_active(0);
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        my $button9 = $self->addButton(
            $grid,
            'Reset',
            'Reset the list of override settings',
            undef,
            8, 10, 11, 12);
        $button9->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'termOverrideHash');

            # Reset widgets
            $combo->set_active(0);
            $entry->set_text('');
            $entry2->set_text('');
            $combo2->set_active(0);
            $combo3->set_active(0);
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        my $button10 = $self->addButton(
            $grid,
            'Clear',
            'Clear the list of override settings',
            undef,
            10, 12, 11, 12);
        $button10->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'termOverrideHash', {});

            # Reset widgets
            $combo->set_active(0);
            $entry->set_text('');
            $entry2->set_text('');
            $combo2->set_active(0);
            $combo3->set_active(0);
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        # Tab complete
        return 1;
    }

    sub override5Tab {

        # Override5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            @columnList, @sigilList, @comboList,
            %comboHash,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->override5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Instruction sigil override settings'],
        );

        # Instruction sigil override settings
        $self->addLabel($grid, '<b>Instruction sigil override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of instruction sigils which should be enabled/disabled when this is the'
            . ' current world, overriding global settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Instruction type', 'text',
            'Enabled', 'bool',
            'Sigil', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 10);

        # Initialise the list
        $self->override5Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Instruction type:',
            1, 3, 10, 11);


        @sigilList = qw(echo perl script multi speed bypass);
        foreach my $item (@sigilList) {

            my $string;

            if ($item eq 'speed') {
                $string = ucfirst($item) . 'walk commands';
            } else {
                $string = ucfirst($item) . ' commands';
            }

            push (@comboList, $string);
            $comboHash{$string} = $item;
        }

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 6, 10, 11);

        my $button = $self->addButton(
            $grid,
            'Enable for this world',
            'Enable the sigil in this world\'s sessions',
            undef,
            6, 9, 10, 11);
        $button->signal_connect('clicked' => sub {

            my (
                $string,
                %hash,
            );

            $string = $comboHash{$comboBox->get_active_text()};
            %hash = $self->getEditHash_hashIV('sigilOverrideHash');
            $hash{$string} = TRUE;
            $self->ivAdd('editHash', 'sigilOverrideHash', \%hash);

            # Refresh the simple list
            $self->override5Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button2 = $self->addButton(
            $grid,
            'Disable for this world',
            'Disable the sigil in this world\'s sessions',
            undef,
            9, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my (
                $string,
                %hash,
            );

            $string = $comboHash{$comboBox->get_active_text()};
            %hash = $self->getEditHash_hashIV('sigilOverrideHash');
            $hash{$string} = FALSE;
            $self->ivAdd('editHash', 'sigilOverrideHash', \%hash);

            # Refresh the simple list
            $self->override5Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button3 = $self->addButton(
            $grid,
            'Use global settings',
            'Enable/disable this sigil according to global settings',
            undef,
            6, 8, 11, 12);
        $button3->signal_connect('clicked' => sub {

            my (
                $string,
                %hash,
            );

            $string = $comboHash{$comboBox->get_active_text()};
            %hash = $self->getEditHash_hashIV('sigilOverrideHash');
            delete $hash{$string};
            $self->ivAdd('editHash', 'sigilOverrideHash', \%hash);

            # Refresh the simple list
            $self->override5Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button4 = $self->addButton(
            $grid,
            'Reset',
            'Reset the list of override settings',
            undef,
            8, 10, 11, 12);
        $button4->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'sigilOverrideHash');

            # Refresh the simple list
            $self->override5Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button5 = $self->addButton(
            $grid,
            'Clear',
            'Clear the list of override settings',
            undef,
            10, 12, 11, 12);
        $button5->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'sigilOverrideHash', {});

            # Refresh the simple list
            $self->override5Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub override5Tab_refreshList {

        # Resets the simple list displayed by $self->override5Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->override5Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('sigilOverrideHash');

        # Compile the simple list data
        foreach my $sigil ('echo', 'perl', 'script', 'multi', 'speed', 'bypass') {

            my $iv;

            if (exists $ivHash{$sigil}) {

                $iv = 'const'. ucfirst($sigil) . 'Sigil';       # e.g. constEchoSigil
                push (@dataList, $sigil, $ivHash{$sigil}, $axmud::CLIENT->$iv);
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub roomsTab {

        # Verbose tab
        #
        # 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 . '->roomsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Rooms');

        # Add tabs to the inner notebook
        $self->rooms1Tab($innerNotebook);
        $self->rooms2Tab($innerNotebook, 'verboseComponentList');
        $self->rooms3Tab($innerNotebook, 'verboseAnchorPatternList', 'verboseAnchorOffset');
        $self->rooms4Tab($innerNotebook, 'verboseAnchorCheckList');
        $self->rooms2Tab($innerNotebook, 'shortComponentList');
        $self->rooms3Tab($innerNotebook, 'shortAnchorPatternList', 'shortAnchorOffset');
        $self->rooms4Tab($innerNotebook, 'shortAnchorCheckList');
        $self->rooms2Tab($innerNotebook, 'briefComponentList');
        $self->rooms3Tab($innerNotebook, 'briefAnchorPatternList', 'briefAnchorOffset');
        $self->rooms4Tab($innerNotebook, 'briefAnchorCheckList');
        $self->rooms11Tab($innerNotebook);
        $self->rooms12Tab($innerNotebook);
        $self->rooms13Tab($innerNotebook);
        $self->rooms14Tab($innerNotebook);
        $self->rooms15Tab($innerNotebook);
        $self->rooms16Tab($innerNotebook);
        $self->rooms17Tab($innerNotebook);
        $self->rooms18Tab($innerNotebook);
        $self->rooms19Tab($innerNotebook);
        $self->rooms20Tab($innerNotebook);
        $self->rooms21Tab($innerNotebook);

        return 1;
    }

    sub rooms1Tab {

        # Rooms1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Room statement components'],
        );

        # Room statement components
        $self->addLabel($grid, '<b>Room statement components</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid, '<i>List of the components that make up room statements in this world</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Component name', 'text',
            'Type', 'text',
            'Fixed size', 'int',
            'Min. size', 'int',
            'Max. size', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->rooms1Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add entries/comboboxes for adding new components
        $self->addLabel($grid, 'Type:',
            1, 2, 8, 9);
        @comboList = $axmud::CLIENT->constComponentTypeList;
        # The first item in the list is 'anchor', which is a special kind of component which doesn't
        #   have a corresponding GA::Obj::Component; remove it from the list
        shift @comboList;
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            2, 6, 8, 9);
        # ->signal_connect appears below...

        $self->addLabel($grid, 'Name:',
            7, 8, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            8, 12, 8, 9);

        $comboBox->signal_connect('changed' => sub {

            my (
                $type, $name,
                %compHash,
            );

            $type = $comboBox->get_active_text();

            # Suggest a name that isn't already taken, and set the $entry to display it
            # First import the hash IV
            %compHash = $self->getEditHash_hashIV('componentHash');
            if (! exists $compHash{$type}) {

                # $type is available as a name, so use the type as the name
                $name = $type;

            } else {

                # Try some alternative names, e.g. 'verb_descrip_1'
                OUTER: for (my $count = 1; $count < 100; $count++) {

                    my $suggestion = $type . '_' . $count;

                    if (! exists $compHash{$suggestion}) {

                        $name = $suggestion;
                        last OUTER;
                    }
                }
            }

            if (defined $name) {

                $entry->set_text($name);
            }
        });

        # Add standard editing buttons to the simple list
        my $button = $self->addButton($grid, 'Add...', 'Add a new room statement component', undef,
            1, 3, 9, 10);
        $button->signal_connect('clicked' => sub {

            my (
                $type, $name, $compObj,
                @comboList,
                %ivHash,
            );

            if ($self->checkEntryIcon($entry)) {

                $type = $comboBox->get_active_text();
                $name = $entry->get_text();

                if ($name eq 'anchor') {

                    $self->showMsgDialogue(
                        'Add component',
                        'warning',
                        'The \'anchor\' component always exists, so it cannot be added to this'
                        . ' list',
                        'ok',
                    );

                } else {

                    # Create a new window component object
                    $compObj = Games::Axmud::Obj::Component->new(
                        $self->session,
                        $self->editObj,
                        $name,
                        $type,
                    );

                    if ($compObj) {

                        # Component object created. Update the IV (don't use ->modifyEditHash_hashIV
                        #   before we need to update the combos, in a moment)
                        %ivHash = $self->getEditHash_hashIV('componentHash');
                        $ivHash{$compObj->name} = $compObj;
                        $self->ivAdd('editHash', 'componentHash', \%ivHash);

                        # Refresh the simple list and reset entry boxes
                        $self->rooms1Tab_refreshList($slWidget, (scalar @columnList / 2));
                        $self->resetEntryBoxes($entry);

                        # Refresh the combo boxes in pages 2, 4 and 6
                        $self->rooms1Tab_refreshCombos(keys %ivHash);
                    }
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Edit...', 'Edit the selected component', undef,
            6, 9, 9, 10);
        $button2->signal_connect('clicked' => sub {

            my (
                $name, $compObj, $childWinObj,
                %ivHash,
            );

            ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                if ($name eq 'anchor') {

                    # (The 'anchor' component shouldn't appear in this tab's simple list, but just
                    #   in case...)
                    $self->showMsgDialogue(
                        'Edit component',
                        'warning',
                        'The \'anchor\' component can\'t be edited',
                        'ok',
                    );

                } else {

                    # Get the corresponding GA::Obj::Component object
                    %ivHash = $self->getEditHash_hashIV('componentHash');
                    $compObj = $ivHash{$name};

                    if ($compObj) {

                        # Open an 'edit' window for the selected component
                        $childWinObj = $self->createFreeWin(
                            'Games::Axmud::EditWin::Component',
                            $self,
                            $self->session,
                            'Edit room component \'' . $name . '\'',
                            $compObj,
                            FALSE,          # Not temporary
                        );
                    }

                    if ($childWinObj) {

                        # When the 'edit' window closes, update widgets and/or IVs
                        $self->add_childDestroy(
                            $childWinObj,
                            'rooms1Tab_refreshList',
                            [$slWidget, (scalar @columnList / 2)],
                        );
                    }
                }
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Dump',
            'Display the selected component in the \'main\' window',
            undef,
            9, 10, 9, 10);
        $button3->signal_connect('clicked' => sub {

            my ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                $self->session->pseudoCmd('listroomcomponent ' . $name, $self->pseudoCmdMode);

                # Refresh the simple list and reset entry boxes
                $self->rooms1Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry);

            }
        });

        my $button4 = $self->addButton($grid, 'Delete', 'Delete the selected component', undef,
            10, 11, 9, 10);
        $button4->signal_connect('clicked' => sub {

            my (
                $name, $hashRef,
                @ivList,
                %ivHash,
            );

            ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                # Update the IV
                %ivHash = $self->getEditHash_hashIV('componentHash');
                delete $ivHash{$name};
                $self->ivAdd('editHash', 'componentHash', \%ivHash);

                # Refresh the simple list and reset entry boxes
                $self->rooms1Tab_refreshList($slWidget, (scalar @columnList / 2));
                $self->resetEntryBoxes($entry);

                # Remove the component from the world's ->verboseComponentList, ->shortComponentList
                #    and ->briefComponentList, if it exists there
                $self->rooms1Tab_updateWorld($name);
                # Refresh the combo boxes in pages 2, 4 and 6
                $self->rooms1Tab_refreshCombos(keys %ivHash);
                # Update the simple lists in pages 2, 4 and 6
                @ivList = ('verboseComponentList', 'shortComponentList', 'briefComponentList');
                foreach my $thisIv (@ivList) {

                    my $thisSimpleList = $self->ivShow('simpleListHash', $thisIv);
                    $self->rooms2Tab_refreshList($thisSimpleList, 2, $thisIv)
                }
            }
        });

        my $button5 = $self->addButton($grid, 'Reset', 'Reset the list of components', undef,
            11, 12, 9, 10);
        $button5->signal_connect('clicked' => sub {

            my %ivHash;

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'componentHash');

            # Refresh the simple list and reset entry boxes
            $self->rooms1Tab_refreshList($slWidget, (scalar @columnList / 2));
            $self->resetEntryBoxes($entry);

            # Refresh the combo boxes in pages 2, 4 and 6
            %ivHash = $self->getEditHash_hashIV('componentHash');

            $self->rooms1Tab_refreshCombos(keys %ivHash);
        });

        # Tab complete
        return 1;
    }

    sub rooms1Tab_refreshList {

        # Resets the simple list displayed by $self->rooms1Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $hashRef,
            @compList, @list, @sortedList, @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->rooms1Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        %ivHash = $self->getEditHash_hashIV('componentHash');
        @compList = sort {lc($a->name) cmp lc($b->name)} (values %ivHash);

        # Sort the list of components, using the standard order supplied by GA::Profile::World
        OUTER: foreach my $type ($axmud::CLIENT->constComponentTypeList) {

            @list = @compList;
            @compList = ();

            INNER: foreach my $compObj (@list) {

                if ($compObj->type eq $type) {

                    push (@sortedList, $compObj);

                } else {

                    # Match this component against a different type in the next iteration of the
                    #   OUTER loop
                    push (@compList, $compObj);
                }
            }
        }

        # Add any stragglers (if there should be any...)
        if (@compList) {

            push (@sortedList, @compList);
        }

        # Compile the simple list data
        foreach my $compObj (@sortedList) {

            push (@dataList,
                $compObj->name,
                $compObj->type,
                $compObj->size,
                $compObj->minSize,
                $compObj->maxSize,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub rooms1Tab_updateWorld {

        # Updates the world's list of room statement components, when one of the components is
        #   removed from ->componentHash
        #
        # Expected arguments
        #   $name   - The name of the component to remove (e.g. 'verb_descrip_1')
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $name, $check) = @_;

        # Local variables
        my @ivList;

        # Check for improper arguments
        if (! defined $name || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms1Tab_updateWorld', @_);
        }

        # The deleted component might still be in any (or, conceivably all) of these IVs
        @ivList = ('verboseComponentList', 'shortComponentList', 'briefComponentList');
        foreach my $iv (@ivList) {

            my (@compList, @modList);

            # Import the IV...
            @compList = $self->getEditHash_listIV($iv);
            foreach my $component (@compList) {

                if ($name ne $component) {

                    push (@modList, $component);
                }
            }

            # ...and store it again
            $self->ivAdd('editHash', $iv, \@modList);
        }

        return 1;
    }

    sub rooms1Tab_refreshCombos {

        # Resets the comboboxes stored in $self->comboHash
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Optional arguments
        #   @comboList  - A list of items to store in each combobox, once it has been emptied (can
        #       be an empty list)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, @comboList) = @_;

        # Local variables
        my @sortedList;

        # (No improper arguments to check)

        # Sort the list of components, and add the anchor line (which doesn't appear in the
        #   ->componentHash IV)
        @sortedList = sort {lc($a) cmp lc($b)} (@comboList);
        push(@sortedList, 'anchor');

        # Update each combobox in turn
        foreach my $combo ($self->ivValues('comboHash')) {

            $self->resetComboBox($combo, @sortedList);
        }

        return 1;
    }

    sub rooms2Tab {

        # Rooms2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #   $iv             - The IV to edit - one of 'verboseComponentList', 'shortComponentList'
        #                       and 'briefComponentList'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $iv, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || ! defined $iv || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms2Tab', @_);
        }

        # Tab setup
        my $grid;

        if ($iv eq 'verboseComponentList') {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _2',
                ['Verbose components'],
            );

            # Verbose components
            $self->addLabel($grid, '<b>Verbose components</b>',
                0, 12, 0, 1);
            $self->addLabel($grid,
                '<i>List of components found in verbose room statements</i>',
                1, 12, 1, 2);

        } elsif ($iv eq 'shortComponentList') {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _5',
                ['Short verbose components'],
            );

            # Short verbose components
            $self->addLabel($grid, '<b>Short verbose components</b>',
                0, 12, 0, 1);
            $self->addLabel($grid,
                '<i>List of components found in short verbose room statements</i>',
                1, 12, 1, 2);

        } else {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _8',
                ['Brief components'],
            );

            # Brief components
            $self->addLabel($grid, '<b>Brief components</b>',
                0, 12, 0, 1);
            $self->addLabel($grid,
                '<i>List of components found in brief room statements</i>',
                1, 12, 1, 2);
        }

        # Add a simple list
        @columnList = (
            'Component name', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the simple list
        $self->rooms2Tab_refreshList($slWidget, scalar (@columnList / 2), $iv);

        # Add editing combos and buttons
        @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('componentHash'));
        push(@comboList, 'anchor');
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            1, 3, 10, 11);

        # Unusual step - store the combo in an IV, so that $self->rooms1Tab can change the list
        #    reference in an IV
        $self->ivAdd('comboHash', $iv, $combo);
        # Also need to store the simple list for the same reason
        $self->ivAdd('simpleListHash', $iv, $slWidget);

        my $button = $self->addButton(
            $grid, 'Add to list', 'Add the selected component to the list', undef,
            3, 5, 10, 11);
        $button->signal_connect('clicked' => sub {

            my (
                $compName, $matchFlag,
                @ivList,
            );

            $compName = $combo->get_active_text();
            if ($compName) {

                # Update the IV
                @ivList = $self->getEditHash_listIV($iv);
                # (If the list already contains an anchor, don't add another one)
                if ($compName eq 'anchor') {

                    OUTER: foreach my $item (@ivList) {

                        if ($item eq 'anchor') {

                            $matchFlag = TRUE;
                            last OUTER;
                        }
                    }

                    if ($matchFlag) {

                        $self->showMsgDialogue(
                            'Add component',
                            'warning',
                            'The list may contain only one \'anchor\' component',
                            'ok',
                        );

                    } else {

                        push (@ivList, $compName);
                        $self->ivAdd('editHash', $iv, \@ivList);
                    }

                } else {

                    push (@ivList, $compName);
                    $self->ivAdd('editHash', $iv, \@ivList);
                }

                # Refresh the simple list
                $self->rooms2Tab_refreshList($slWidget, scalar (@columnList / 2), $iv);
            }
        });

        my $button2 = $self->addButton($grid, 'Edit...', 'Edit the selected component', undef,
            6, 8, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my (
                $name, $compObj, $childWinObj,
                %ivHash,
            );

            ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                if ($name eq 'anchor') {

                    $self->showMsgDialogue(
                        'Edit component',
                        'warning',
                        'The \'anchor\' component can\'t be edited',
                        'ok',
                    );

                } else {

                    # Get the corresponding GA::Obj::Component object
                    %ivHash = $self->getEditHash_hashIV('componentHash');
                    $compObj = $ivHash{$name};

                    if ($compObj) {

                        # Open an 'edit' window for the selected component
                        $childWinObj = $self->createFreeWin(
                            'Games::Axmud::EditWin::Component',
                            $self,
                            $self->session,
                            'Edit room component \'' . $name . '\'',
                            $compObj,
                            FALSE,          # Not temporary
                        );
                    }

                    if ($childWinObj) {

                        # When the 'edit' window closes, update widgets and/or IVs
                        $self->add_childDestroy(
                            $childWinObj,
                            'rooms2Tab_refreshList',
                            [$slWidget, (scalar @columnList / 2), $iv],
                        );
                    }
                }
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Dump',
            'Display the selected component in the \'main\' window',
            undef,
            8, 10, 10, 11);
        $button3->signal_connect('clicked' => sub {

            my ($name) = $self->getSimpleListData($slWidget, 0);
            if (defined $name) {

                $self->session->pseudoCmd('listroomcomponent ' . $name, $self->pseudoCmdMode);

                # Refresh the simple list
                $self->rooms2Tab_refreshList($slWidget, scalar (@columnList / 2), $iv);
            }
        });

        my $button4 = $self->addButton($grid,
            'Remove from list',
            'Remove the selected component from this list (but don\'t delete it)',
            undef,
            10, 12, 10, 11);
        $button4->signal_connect('clicked' => sub {

            my (
                $rowNum,
                @ivList,
            );

            # Get the selected row
            ($rowNum) = $slWidget->get_selected_indices();
            if (defined $rowNum) {

                # Update the IV
                @ivList = $self->getEditHash_listIV($iv);

                splice (@ivList, $rowNum, 1);
                $self->ivAdd('editHash', $iv, \@ivList);

                # Refresh the simple list
                $self->rooms2Tab_refreshList($slWidget, scalar (@columnList / 2), $iv);
            }
        });

        my $button5 = $self->addButton($grid,
            'Move up', 'Move the selected component up the list', undef,
            1, 3, 11, 12);
        $button5->signal_connect('clicked' => sub {

            my (
                $rowNum, $compName,
                @ivList,
            );

            # Get the selected row
            ($rowNum) = $slWidget->get_selected_indices();
            # (Don't move a component already at the top of the list...)
            if (defined $rowNum && $rowNum > 0) {

                # Update the IV
                @ivList = $self->getEditHash_listIV($iv);

                $compName = splice (@ivList, $rowNum, 1);
                splice (@ivList, ($rowNum - 1), 0, $compName);
                $self->ivAdd('editHash', $iv, \@ivList);

                # Refresh the simple list
                $self->rooms2Tab_refreshList($slWidget, scalar (@columnList / 2), $iv);

                # The component should still be highlighted, after being moved up
                $slWidget->select($rowNum - 1);
            }
        });

        my $button6 = $self->addButton($grid,
            'Move down', 'Move the selected component down the list', undef,
            10, 12, 11, 12);
        $button6->signal_connect('clicked' => sub {

            my (
                $rowNum, $listRef, $compName,
                @ivList,
            );

            # Get the selected row
            ($rowNum) = $slWidget->get_selected_indices();
            if (defined $rowNum) {

                # Update the IV
                @ivList = $self->getEditHash_listIV($iv);

                # Don't move a component that is already at the bottom of the list
                if ($rowNum < (scalar @ivList - 1)) {

                    $compName = splice (@ivList, $rowNum, 1);
                    splice (@ivList, ($rowNum + 1), 0, $compName);
                    $self->ivAdd('editHash', $iv, \@ivList);

                    # Refresh the simple list
                    $self->rooms2Tab_refreshList($slWidget, scalar (@columnList / 2), $iv);

                    # The component should still be highlighted, after being moved up
                    $slWidget->select($rowNum + 1);
                }
            }
        });

        # Tab complete
        return 1;
    }

    sub rooms2Tab_refreshList {

        # Resets the simple list displayed by $self->rooms2Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #   $iv         - The IV to display - one of 'verboseComponentList', 'shortComponentList'
        #                   and 'briefComponentList'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $iv, $check) = @_;

        # Local variables
        my (
            $slWidgetRef,
            @compList, @dataList,
            %compHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $iv || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->rooms2Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        @compList = $self->getEditHash_listIV($iv);
        # The component object itself may have been created (or modified) since the 'edit' window
        #   was opened
        %compHash = $self->getEditHash_hashIV('componentHash');

        # Compile the simple list data
        foreach my $compName (@compList) {

            my $compObj;

            # 'anchor' is a special type of room statement component that doesn't have its own
            #   component object
            if ($compName eq 'anchor') {

                push (@dataList, 'anchor', 'anchor');

            } else {

                $compObj = $compHash{$compName};
                push (@dataList,
                    $compObj->name,
                    $compObj->type,
                );
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub rooms3Tab {

        # Rooms1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #   $listIV         - The list IV to edit - one of 'verboseAnchorPatternList',
        #                       'shortAnchorPatternList' and 'briefAnchorPatternList'
        #   $offsetIV       - The accompanying scalar IV to edit - one of 'verboseAnchorOffset',
        #                       'shortAnchorOffset' and 'briefAnchorOffset'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $listIV, $offsetIV, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $innerNotebook || ! defined $listIV || ! defined $offsetIV
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms3Tab', @_);
        }

        # Tab setup
        my $grid;

        if ($listIV eq 'verboseAnchorPatternList') {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _3',
                ['Verbose anchor patterns'],
            );

            # Verbose anchor patterns
            $self->addLabel($grid, '<b>Verbose anchor patterns</b>',
                0, 10, 0, 1);
            $self->addLabel($grid,
                '<i>List of patterns which mark a line as a verbose anchor line</i>',
                1, 10, 1, 2);

        } elsif ($listIV eq 'shortAnchorPatternList') {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _6',
                ['Short verbose anchor patterns'],
            );

            # Short verbose anchor patterns
            $self->addLabel($grid, '<b>Short verbose anchor patterns</b>',
                0, 10, 0, 1);
            $self->addLabel($grid,
                '<i>List of patterns which mark a line as a short verbose anchor line</i>',
                1, 10, 1, 2);

        } elsif ($listIV eq 'briefAnchorPatternList') {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _9',
                ['Brief anchor patterns'],
            );

            # Brief anchor patterns
            $self->addLabel($grid, '<b>Brief anchor patterns</b>',
                0, 10, 0, 1);
            $self->addLabel($grid,
                '<i>List of patterns which mark a line as a brief anchor line</i>',
                1, 10, 1, 2);
        }

        $self->addRegexButton($grid,
            [
                'list', $listIV,
            ],
            10, 12, 0, 2);

        $self->addTextView($grid, $listIV, TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid,
            '<i>Anchor line\'s relationship with other components in the component list</i>',
            1, 12, 6, 7);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, 'Mode -1', $offsetIV,
            -1,         # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 4, 7, 8);
        $self->addLabel($grid, 'Anchor line shares a line with the component before it',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, 'Mode 0', $offsetIV, 0, TRUE,
            1, 4, 8, 9);
        $self->addLabel($grid, 'Anchor line does not share its line with any other component',
            4, 12, 8, 9);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, 'Mode +1', $offsetIV, 1, TRUE,
            1, 4, 9, 10);
        $self->addLabel($grid, 'Anchor line shares a line with the component after it',
            4, 12, 9, 10);

        # Tab complete
        return 1;
    }

    sub rooms4Tab {

        # Rooms4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #   $iv             - The IV to edit - one of 'verboseAnchorCheckList',
        #                       'shortAnchorCheckList' and 'briefAnchorCheckList'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $iv, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || ! defined $iv || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms4Tab', @_);
        }

        # Tab setup
        my $grid;

        if ($iv eq 'verboseAnchorCheckList') {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _4',
                ['Verbose check patterns'],
            );

            # Verbose check patterns
            $self->addLabel($grid, '<b>Verbose check patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($grid,
                '<i>List of patterns found near a verbose anchor line</i>',
                1, 12, 1, 2);

        } elsif ($iv eq 'shortAnchorCheckList') {

            $grid = $self->addTab(
                $innerNotebook,
                'Page _7',
                ['Short check patterns'],
            );

            # Short check patterns
            $self->addLabel($grid, '<b>Short check patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($grid,
                '<i>List of patterns found near a short verbose anchor line</i>',
                1, 12, 1, 2);

        } else {

            $grid = $self->addTab(
                $innerNotebook,
                'Page 1_0',
                ['Brief check patterns'],
            );

            # Brief check patterns
            $self->addLabel($grid, '<b>Brief check patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($grid,
                '<i>List of patterns found near a brief anchor line</i>',
                1, 12, 1, 2);
        }

        # Add a simple list
        @columnList = (
            'Offset', 'int',
            'Pattern', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the simple list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), $iv);

        # Add editing widgets
        $self->addLabel($grid, 'Line offset:',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'int', -16, 16,
            3, 6, 10, 11);
        $self->addLabel($grid, 'Pattern:',
            7, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            $iv,
            11, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($offset, $pattern);

            $offset = $entry->get_text();
            $pattern = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV($iv, undef, FALSE, $offset, $pattern);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV($slWidget, scalar (@columnList / 2), $iv);
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub rooms11Tab {

        # Rooms11 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @tagList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms11Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 11',
            ['Other room statement markers'],
        );

        # Other room statement markers
        $self->addLabel($grid, '<b>Other room statement markers</b>',
            0, 10, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'notAnchorPatternList',
                'scalar', 'verboseFinalPattern',
                'scalar', 'shortFinalPattern',
                'scalar', 'briefFinalPattern',
            ],
            10, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef,
            'OFF - Basic mapping mode not required (suitable for most worlds)',
            'basicMappingFlag',
            FALSE,         # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group,
            'ON - Use basic mapping (room statements don\'t contain matchable text: usually, no'
            . ' list of exits)',
            'basicMappingFlag',
            TRUE,
            TRUE,
            1, 12, 2, 3);

        # (Not anchor patterns)
        $self->addLabel($grid,
            '<i>Patterns which mean a line is definitely not an anchor line</i>',
            1, 12, 3, 4);

        $self->addTextView($grid, 'notAnchorPatternList', TRUE,
            1, 12, 4, 8,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        # (Room statement termination patterns)
        $self->addLabel(
            $grid,
            '<i>Patterns which mean the whole room statement has been received (usually not'
            . ' necessary)</i>',
            1, 12, 8, 9);

        $self->addLabel($grid, 'Verbose statement terminator',
            1, 4, 9, 10);
        $self->addEntry($grid, 'verboseFinalPattern', TRUE,
            4, 12, 9, 10);
        $self->addLabel($grid, 'Short verbose statement terminator',
            1, 4, 10, 11);
        $self->addEntry($grid, 'shortFinalPattern', TRUE,
            4, 12, 10, 11);
        $self->addLabel($grid, 'Brief statement terminator',
            1, 4, 11, 12);
        $self->addEntry($grid, 'briefFinalPattern', TRUE,
            4, 12, 11, 12);

        # (MXP tag properties)
        $self->addLabel(
            $grid,
            '<i>MXP tag properties marking the beginning/end of a room statement (optional; set'
            . ' one or both)</i>',
            1, 12, 13, 14);

        @tagList = ('RoomName', 'RoomDesc', 'RoomExit', 'RoomNum');
        $self->addLabel($grid, 'Statement starts at',
            1, 3, 14, 15);
        $self->addComboBox($grid, 'mxpRoomTagStart', \@tagList, '',
            FALSE,           # 'undef' value used
            3, 6, 14, 15);
        $self->addLabel($grid, 'Statement stops at',
            6, 9, 14, 15);
        $self->addComboBox($grid, 'mxpRoomTagStop', \@tagList, '',
            FALSE,           # 'undef' value used
            9, 12, 14, 15);

        # Tab complete
        return 1;
    }

    sub rooms12Tab {

        # Rooms12 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms12Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 12',
            ['Verbose exit delimiter strings/marker patterns'],
        );

        # Verbose exit delimiter/marker patterns
        $self->addLabel($grid,
            '<b>Verbose exit delimiter strings/marker patterns</b>',
            0, 10, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'verboseExitLeftMarkerList',
                'list', 'verboseExitRightMarkerList',
            ],
            10, 12, 0, 1);

        my $checkButton = $self->addCheckButton(
            $grid,
            'Split exits into single characters, e.g. for NSEW',
            'verboseExitSplitCharFlag',
            TRUE,
            1, 12, 1, 2);

        $self->addLabel($grid, '<i>Delimiters (<b>not</b> patterns/regular expressions)</i>',
            1, 6, 2, 3);
        $self->addTextView($grid, 'verboseExitDelimiterList', TRUE,
            1, 6, 3, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addLabel($grid, '<i>Non-delimiters (<b>not</b> patterns/regular expressions)</i>',
            7, 12, 2, 3);
        $self->addTextView($grid, 'verboseExitNonDelimiterList', TRUE,
            7, 12, 3, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, '<i>Left-side marker patterns</i>',
            1, 6, 6, 7);
        $self->addTextView($grid, 'verboseExitLeftMarkerList', TRUE,
            1, 6, 7, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addLabel($grid, '<i>Right-side marker patterns</i>',
            7, 12, 6, 7);
        $self->addTextView($grid, 'verboseExitRightMarkerList', TRUE,
            7, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub rooms13Tab {

        # Rooms13 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms13Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 13',
            ['Brief exit delimiter strings/marker patterns'],
        );

        # Brief exit delimiter/marker patterns
        $self->addLabel($grid,
            '<b>Brief exit delimiter strings/marker patterns</b>',
            0, 12, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'briefExitLeftMarkerList',
                'list', 'briefExitRightMarkerList',
            ],
            10, 12, 0, 1);

        my $checkButton = $self->addCheckButton(
            $grid,
            'Split exits into single characters, e.g. for NSEW',
            'briefExitSplitCharFlag',
            TRUE,
            1, 12, 1, 2);

        $self->addLabel($grid, '<i>Delimiters (<b>not</b> patterns/regular expressions)</i>',
            1, 6, 2, 3);
        $self->addTextView($grid, 'briefExitDelimiterList', TRUE,
            1, 6, 3, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addLabel($grid, '<i>Non-delimiters (<b>not</b> patterns/regular expressions)</i>',
            7, 12, 2, 3);
        $self->addTextView($grid, 'briefExitNonDelimiterList', TRUE,
            7, 12, 3, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, '<i>Left-side marker patterns</i>',
            1, 6, 6, 7);
        $self->addTextView($grid, 'briefExitLeftMarkerList', TRUE,
            1, 6, 7, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addLabel($grid, '<i>Right-side marker patterns</i>',
            7, 12, 6, 7);
        $self->addTextView($grid, 'briefExitRightMarkerList', TRUE,
            7, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub rooms14Tab {

        # Rooms14 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms14Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 14',
            ['Exit state strings'],
        );

        # Exit state strings
        $self->addLabel($grid, '<b>Exit state strings</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Strings (<b>not</b> patterns/regular expressions) which give more information about'
            . ' exits in an exit list</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Exit state', 'text',
            'Start string', 'text',
            'Middle string', 'text',
            'End string', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the simple list
        $self->rooms14Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Exit state',
            1, 3, 8, 9);
        @comboList = (
            'open',
            'closed',
            'locked',
            'secret',
            'secret_open',
            'secret_closed',
            'secret_locked',
            'impass',
            'dark',
            'danger',
            'emphasis',
            'other',
            'ignore',
            # Put 'normal' at the end, since it's the default exit state (but is probably never
            #   added to $self->ediObj->exitStateStringList)
            'normal',
        );
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($grid, '(or) specify an assisted move',
            7, 9, 8, 9);
        my $entry = $self->addEntry($grid, undef, TRUE,
            9, 12, 8, 9);

        $self->addLabel($grid, 'Start string',
            1, 3, 9, 10);
        my $entry2 = $self->addEntry($grid, undef, TRUE,
            3, 6, 9, 10);

        $self->addLabel($grid, 'Middle string',
            7, 9, 9, 10);
        my $entry3 = $self->addEntry($grid, undef, TRUE,
            9, 12, 9, 10);

        $self->addLabel($grid, 'End string',
            1, 3, 10, 11);
        my $entry4 = $self->addEntry($grid, undef, TRUE,
            3, 6, 10, 11);

        my $button = $self->addButton(
            $grid, 'Add', 'Add this exit state', undef,
            1, 4, 11, 12);
        $button->signal_connect('clicked' => sub {

            my (
                $text, $text2, $state, $start, $middle, $end,
                @ivList,
            );

            $text = $combo->get_active_text();
            $text2 = $entry->get_text();
            if (defined $text2 && $text2 ne '') {
                $state = $text2;
            } else {
                $state = $text;
            }

            $start = $entry2->get_text();
            if (! $start) {

                # Use an empty string, rather than 'undef'
                $start = '';
            }

            $middle = $entry3->get_text();
            if (! $middle) {

                $middle = '';
            }

            $end = $entry4->get_text();
            if (! $end) {

                $end = '';
            }

            # Update the IV
            @ivList = $self->getEditHash_listIV('exitStateStringList');
            push (@ivList, $state, $start, $middle, $end);
            $self->ivAdd('editHash', 'exitStateStringList', \@ivList);

            # Refresh the simple list
            $self->rooms14Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        my $button2 = $self->addButton(
            $grid, 'Delete', 'Delete the selected exit state from the list', undef,
            4, 6, 11, 12);
        $button2->signal_connect('clicked' => sub {

            my (
                $rowNum,
                @ivList,
            );

            # Get the selected row
            ($rowNum) = $slWidget->get_selected_indices();
            if (defined $rowNum) {

                # Update the IV
                @ivList = $self->getEditHash_listIV('exitStateStringList');

                splice (@ivList, ($rowNum * 4), 4);
                $self->ivAdd('editHash', 'exitStateStringList', \@ivList);

                # Refresh the simple list
                $self->rooms14Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button3 = $self->addButton(
            $grid, 'Reset', 'Reset the list of exit states', undef,
            10, 12, 11, 12);
        $button3->signal_connect('clicked' => sub {

            $self->ivDelete('editHash', 'exitStateStringList');

            # Refresh the simple list
            $self->rooms14Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub rooms14Tab_refreshList {

        # Resets the simple list displayed by $self->rooms14Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->rooms14Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        @dataList = $self->getEditHash_listIV('exitStateStringList');
        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub rooms15Tab {

        # Rooms15 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms15Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 15',
            ['Exit state colour tags'],
        );

        # Exit state colour tags
        $self->addLabel($grid, '<b>Exit state colour tags</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of colour tags which specify an exit state, when they occur at the beginning'
            . ' of an exit</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Colour tag', 'text',
            'Exit state', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 9);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'exitStateTagHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Colour tag',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, \&rooms15Tab_checkEntry, undef, undef,
            3, 6, 9, 10);

        $self->addLabel($grid, 'Exit state',
            1, 3, 10, 11);
        @comboList = (
            'open',
            'closed',
            'locked',
            'secret',
            'secret_open',
            'secret_closed',
            'secret_locked',
            'impass',
            'dark',
            'danger',
            'emphasis',
            'other',
            'ignore',
            # Put 'normal' at the end, since it's the default exit state (but is probably never
            #   added to $self->ediObj->exitStateStringList)
            'normal',
        );
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            3, 6, 10, 11);

        $self->addLabel($grid, '(or) specify an assisted move',
            7, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'exitStateTagHash',
            11,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($tag, $text, $text2, $state);

            $tag = $entry->get_text();

            $text = $combo->get_active_text();
            $text2 = $entry2->get_text();
            if (defined $text2 && $text2 ne '') {
                $state = $text2;
            } else {
                $state = $text;
            }

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('exitStateTagHash', $tag, $state);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'exitStateTagHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub rooms15Tab_checkEntry {

        # Called by $self->rooms15Tab to check the text in the Gtk3::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk3::Entry
        #
        # Return values
        #   'undef' on improper arguments or if $text is invalid
        #   1 if $text is valid

        my ($self, $text, $check) = @_;

        # Local variables
        my $result;

        # Check for improper arguments
        if (! defined $text || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms15Tab_checkEntry', @_);
        }

        # $text can be an empty string, or any valid Axmud colour tag
        if ($text eq '') {

            return 1;

        } else {

            ($result) = $axmud::CLIENT->checkColourTags($text);
            if (defined $result) {
                return 1;
            } else {
                return undef;
            }
        }
    }

    sub rooms16Tab {

        # Rooms16 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms16Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 16',
            ['Exit aliases'],
        );

        # Exit aliases
        $self->addLabel($grid, '<b>Exit aliases</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of patterns, which appear in exit lists, but which represent one or more other'
            . ' exits</i>',
            1, 12, 1, 2);
        $self->addLabel($grid,
            '<i>e.g. \'compass\' - representing the exits \'n s e w nw ne sw se\'</i>',
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Exit alias (pattern)', 'text',
            'Replacement string', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'exitAliasHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Exit alias',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Replacement string',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'exitAliasHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($alias, $string);

            $alias = $entry->get_text();
            $string = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('exitAliasHash', $alias, $string);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'exitAliasHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub rooms17Tab {

        # Rooms17 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms17Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 17',
            ['Exit state patterns', 'Duplicate exit replacement string'],
        );

        # Exit state patterns
        $self->addLabel($grid, '<b>Exit state patterns</b>',
            0, 10, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'exitStatePatternList',
            ],
            10, 12, 0, 1);

        $self->addLabel(
            $grid,
            '<i>If exit delimiters interfere with state strings, matching parts of the exit area'
            . ' removed before delimiters are applied</i>',
            1, 12, 1, 2);
        $self->addTextView($grid, 'exitStatePatternList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        # Duplicate exit replacement string
        $self->addLabel($grid, '<b>Duplicate exit replacement string</b>',
            0, 12, 6, 7);
        $self->addLabel(
            $grid,
            '<i>If an exit appears in an exit list twice, the second one is converted to this'
            . ' string. <b>@@@</b> is substituted for the duplicate</i>',
            0, 12, 7, 8);
        $self->addEntryWithButton($grid, 'duplicateReplaceString', TRUE,
            1, 12, 8, 9,
            64, 64);                    # Use high max characters for layout reasons

        # Tab complete
        return 1;
    }

    sub rooms18Tab {

        # Rooms18 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms18Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 18',
            ['Exit remove/info patterns'],
        );

        # Exit remove/info patterns
        $self->addLabel($grid, '<b>Exit remove/info patterns</b>',
            0, 10, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'exitRemovePatternList',
                'list', 'exitInfoPatternList',
            ],
            10, 12, 0, 1);

        $self->addLabel(
            $grid,
            '<i>Parts of the exit which match these patterns are removed, before the exit is'
            . ' processed</i>',
            1, 12, 1, 2);
        $self->addTextView($grid, 'exitRemovePatternList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel(
            $grid,
            '<i>Parts of the exit which match these patterns are removed (but the first'
            . ' group substring is stored)</i>',
            1, 12, 6, 7);
        $self->addTextView($grid, 'exitInfoPatternList', TRUE,
            1, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub rooms19Tab {

        # Rooms19 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms19Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 19',
            ['Contents list patterns'],
        );

        # Contents list patterns
        $self->addLabel($grid, '<b>Contents list patterns</b>',
            0, 10, 0, 2);
        $self->addLabel(
            $grid, '<i>List of patterns seen in contents lists (which are not parsed)</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'contentPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'contentPatternList', TRUE,
            1, 12, 4, 12,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub rooms20Tab {

        # Rooms20 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms20Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 20',
            ['Special contents patterns'],
        );

        # Special contents patterns
        $self->addLabel($grid, '<b>Special contents patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of patterns matching any line in the \'verb_special\' component</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Replacement contents', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'specialPatternHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 6, 8, 9);

        $self->addLabel($grid, 'Replacement',
            7, 8, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            8, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'specialPatternHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $replace);

            $pattern = $entry->get_text();
            $replace = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('specialPatternHash', $pattern, $replace);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'specialPatternHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub rooms21Tab {

        # Rooms21 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->rooms21Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 21',
            ['Room command delimiter strings/marker patterns'],
        );

        # Room command delimiter strings/marker patterns
        $self->addLabel($grid,
            '<b>Room command delimiter strings/marker patterns</b>',
            0, 12, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'roomCmdLeftMarkerList',
                'list', 'roomCmdRightMarkerList',
            ],
            10, 12, 0, 1);

        $self->addLabel($grid, '<i>Delimiters (<b>not</b> patterns)</i>',
            1, 4, 1, 2);
        $self->addTextView($grid, 'roomCmdDelimiterList', TRUE,
            1, 4, 2, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addLabel($grid, '<i>Non-delimiters (<b>not</b> patterns)</i>',
            4, 8, 1, 2);
        $self->addTextView($grid, 'roomCmdNonDelimiterList', TRUE,
            4, 8, 2, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addLabel($grid, '<i>Ignorable commands (<b>not</b> patterns)</i>',
            8, 12, 1, 2);
        $self->addTextView($grid, 'roomCmdIgnoreList', TRUE,
            8, 12, 2, 11,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, '<i>Left-side marker patterns</i>',
            1, 4, 6, 7);
        $self->addTextView($grid, 'roomCmdLeftMarkerList', TRUE,
            1, 4, 7, 11,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        $self->addLabel($grid, '<i>Right-side marker patterns</i>',
            4, 8, 6, 7);
        $self->addTextView($grid, 'roomCmdRightMarkerList', TRUE,
            4, 8, 7, 11,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        my $checkButton = $self->addCheckButton(
            $grid,
            'Split room commands into single-character commands',
            'roomCmdSplitCharFlag',
            TRUE,
            1, 12, 11, 12);

        # Tab complete
        return 1;
    }

    sub movesTab {

        # Moves tab
        #
        # 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 . '->movesTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Mo_ves');

        # Add tabs to the inner notebook
        $self->moves1Tab($innerNotebook);
        $self->moves2Tab($innerNotebook);
        $self->moves3Tab($innerNotebook);
        $self->moves4Tab($innerNotebook);
        $self->moves5Tab($innerNotebook);
        $self->moves6Tab($innerNotebook);

        return 1;
    }

    sub moves1Tab {

        # Moves1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->moves1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Failed exit patterns', 'Dark room patterns'],
        );

        # Failed exit patterns
        $self->addLabel($grid, '<b>Failed exit patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns seen when the character fails to move</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'failExitPatternList',
                'list', 'darkRoomPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'failExitPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Dark room patterns
        $self->addLabel($grid, '<b>Dark room patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns seen when the character moves to a room with no light</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'darkRoomPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub moves2Tab {

        # Moves2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->moves2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Door patterns', 'Locked door patterns'],
        );

        # Door patterns
        $self->addLabel($grid, '<b>Door patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns seen when the character bumps into a door</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'doorPatternList',
                'list', 'lockedPatternList',
            ],
            10, 12, 0, 4);
        $self->addTextView($grid, 'doorPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Locked door patterns
        $self->addLabel($grid, '<b>Locked door patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Patterns seen when the character bumps into a locked door</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'lockedPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub moves3Tab {

        # Moves3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->moves3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Involuntary exit patterns', 'Unspecified room patterns'],
        );

        # Involuntary exit patterns
        $self->addLabel($grid, '<b>Involuntary exit patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns seen when the character is moved involuntarily</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'involuntaryExitPatternList',
                'list', 'unspecifiedRoomPatternList',
            ],
            10, 12, 0, 4);
        $self->addTextView($grid, 'involuntaryExitPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Unspecified room patterns
        $self->addLabel($grid, '<b>Unspecified room patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns seen when the character moves to a room without a recognisable room'
            . ' statement</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'unspecifiedRoomPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub moves4Tab {

        # Moves4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->moves4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Transient exit patterns'],
        );

        # Transient exit patterns
        $self->addLabel($grid, '<b>Transient exit patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Patterns matching exits which appear unpredictably (e.g. the entrance to a moving'
            . ' vehicle)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Transient exit pattern', 'text',
            'Destination room #', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'transientExitPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Transient exit pattern',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($grid, '(Optional) desination room #',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 0, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'transientExitPatternList',
            11, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $destRoom);

            $pattern = $entry->get_text();
            $destRoom = $entry2->get_text();
            if ($destRoom eq '' || $destRoom eq '0') {

                $destRoom = undef;
            }

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV('transientExitPatternList',
                    undef, FALSE,
                    $pattern, $destRoom,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'transientExitPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub moves5Tab {

        # Moves5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->moves5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Follow patterns'],
        );

        # Follow patterns
        $self->addLabel($grid, '<b>Follow patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Patterns seen when the character moves by following someone, and a new room'
            . ' statement is then received</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Follow pattern', 'text',
            'Direction substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'followPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Follow pattern',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($grid, 'Substring # for direction',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'followPatternList',
            11, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV('followPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'followPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub moves6Tab {

        # Moves6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->moves6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Follow anchor patterns'],
        );

        # Follow anchor patterns
        $self->addLabel($grid, '<b>Follow anchor patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Patterns seen when the character moves by following someone, but no new room'
            . ' statement is then received</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Follow anchor pattern', 'text',
            'Direction substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'followAnchorPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Follow anchor pattern',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($grid, 'Substring # for direction',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'followAnchorPatternList',
            11, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV('followAnchorPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'followAnchorPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub statusTab {

        # Status tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $taskObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->statusTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'S_tatus');

        # Create a temporary status task, so we can access its IVs
        $taskObj = Games::Axmud::Task::Status->new($self->session);

        # Add tabs to the inner notebook
        $self->status1Tab($innerNotebook);
        $self->status2Tab($innerNotebook, $taskObj);
        $self->status3Tab($innerNotebook, $taskObj);
        $self->status4Tab($innerNotebook);
        $self->status5Tab($innerNotebook);
        $self->status6Tab($innerNotebook, $taskObj);
        $self->status7Tab($innerNotebook);
        $self->status8Tab($innerNotebook);
        $self->status9Tab($innerNotebook);
        $self->status10Tab($innerNotebook);
        $self->status11Tab($innerNotebook);
        $self->status12Tab($innerNotebook);
        $self->status13Tab($innerNotebook);
        $self->status14Tab($innerNotebook);

        return 1;
    }

    sub status1Tab {

        # Status1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Status task commands'],
        );

        # Status task commands
        $self->addLabel($grid, '<b>Status task commands</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Default list of commands sent to the world to update the Status task\'s'
            . ' variables (inherited by each</i>',
            1, 12, 1, 2);
        $self->addLabel($grid,
            '<i>new character profile - edit the current character profile to change the commands'
            . ' actually sent)</i>',
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Command', 'text',
            'How often to send', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statusCmdHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Command',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Sent how often (in seconds):',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'statusCmdHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($cmd, $time);

            $cmd = $entry->get_text();
            $time = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('statusCmdHash', $cmd, $time);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statusCmdHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub status2Tab {

        # Status2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #   $taskObj        - A temporary Status task object
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $taskObj, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || ! defined $taskObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Status task window format'],
        );

        # Status task window format
        $self->addLabel($grid, '<b>Status task window format</b>',
            0, 6, 0, 1);
        $self->addLabel(
            $grid,
            '<i>How the Status task displays data in its task window (format: </i>ordinary text'
            . ' @variable_name@...<i> )</i>',
            1, 11, 1, 2);
        my $textView = $self->addTextView($grid, 'statusFormatList', TRUE,
            1, 11, 2, 11,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        # Add a combo box displaying the list of Status task display variables
        @comboList = sort {lc($a) cmp lc($b)} (
            $taskObj->ivKeys('constCharVarHash'),
            $taskObj->ivKeys('constFixedVarHash'),
            $taskObj->ivKeys('constPseudoVarHash'),
            $taskObj->ivKeys('constLocalVarHash'),
            $taskObj->ivKeys('constCounterVarHash'),
        );
        $self->addLabel($grid, 'Variable:',
            1, 2, 11, 12);
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            2, 3, 11, 12);

        # Create some customised buttons for this tab
        # Add a button to insert the currently selected display variable into the textview, wherever
        #   the cursor is now
        my $button = Gtk3::Button->new('Insert');
        $button->signal_connect('clicked' => sub {

            my ($text, $buffer);

            $text = $comboBox->get_active_text();
            $buffer = $textView->get_buffer();
            $buffer->insert_at_cursor('@' . $text . '@');
        });
        $button->set_tooltip_text('Insert the selected variable at the cursor');
        $grid->attach($button, 3, 11, 2, 1);

        # Add a button to reset the contents of the textview to that stored by the profile
        my $button2 = Gtk3::Button->new('Reset');
        $button2->signal_connect('clicked' => sub {

            my $buffer = $textView->get_buffer();
            $buffer->set_text(join("\n", $self->editObj->statusFormatList));
        });
        $button2->set_tooltip_text('Reset the Status task display');
        $grid->attach($button2, 5, 11, 2, 1);

        # Add a button to use the Status task's default display format list
        my $button3 = Gtk3::Button->new('Default');
        $button3->signal_connect('clicked' => sub {

            my $buffer = $textView->get_buffer();
            $buffer->set_text(join("\n", $taskObj->defaultFormatList));
        });
        $button3->set_tooltip_text('Use the default Status task display');
        $grid->attach($button3, 7, 11, 2, 1);

        # Add a button to empty the textview
        my $button4 = Gtk3::Button->new('Clear');
        $button4->signal_connect('clicked' => sub {

            my $buffer = $textView->get_buffer();
            $buffer->set_text('');
        });
        $button4->set_tooltip_text('Clear the Status task display');
        $grid->attach($button4, 9, 11, 2, 1);

        # Tab complete
        return 1;
    }

    sub status3Tab {

        # Status3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #   $taskObj        - A temporary Status task object
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $taskObj, $check) = @_;

        # Local variables
        my (@columnList, @comboList, @colourList);

        # Check for improper arguments
        if (! defined $innerNotebook || ! defined $taskObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Status task - gauges'],
        );

        # Status task - gauges
        $self->addLabel(
            $grid,
            '<b>Status task - gauges</b>'
            . '   <i>How the Status task displays gauges in the \'main\' window</i>',
            0, 12, 0, 1);
        # Add a simple list
        @columnList = (
            'Variable', 'text',
            'Max variable', 'text',
            'Add', 'bool',
            'Label', 'text',
            'Full col', 'text',
            'Empty col', 'text',
            'Label col', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 1, 6);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'gaugeFormatList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Variable:',
            1, 2, 6, 7);

        @comboList = sort {lc($a) cmp lc($b)} (
            $taskObj->ivKeys('constCharVarHash'),
            $taskObj->ivKeys('constFixedVarHash'),
        );
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            2, 6, 6, 7);
        $self->addLabel($grid, 'or custom:',
            6, 7, 6, 7);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            7, 10, 6, 7);

        $self->addLabel($grid, 'Maximum var:',
            1, 2, 7, 8);
        my $comboBox2 = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            2, 6, 7, 8);
        $self->addLabel($grid, 'or custom:',
            6, 7, 7, 8);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            7, 10, 7, 8);

        my $checkButton = $self->addCheckButton($grid, 'Not maximum', undef, TRUE,
            10, 12, 7, 8);

        @colourList = ($axmud::CLIENT->constColourTagList, $axmud::CLIENT->constBoldColourTagList);
        $self->addLabel($grid, 'Full colour:',
            1, 2, 8, 9);
        my $comboBox3 = $self->addComboBox($grid, undef, [@colourList], '',
            TRUE,               # No 'undef' value used
            2, 6, 8, 9);
        $self->addLabel($grid, 'or xterm/RGB:',
            6, 7, 8, 9);
        my $entry3 = $self->addEntryWithIcon($grid, undef, \&status3Tab_checkEntry, undef, undef,
            7, 10, 8, 9);

        $self->addLabel($grid, 'Empty colour:',
            1, 2, 9, 10);
        my $comboBox4 = $self->addComboBox($grid, undef, [@colourList], '',
            TRUE,               # No 'undef' value used
            2, 6, 9, 10);
        $self->addLabel($grid, 'or xterm/RGB:',
            6, 7, 9, 10);
        my $entry4 = $self->addEntryWithIcon($grid, undef, \&status3Tab_checkEntry, undef, undef,
            7, 10, 9, 10);

        $self->addLabel($grid, 'Label colour:',
            1, 2, 10, 11);
        my $comboBox5 = $self->addComboBox($grid, undef, [@colourList], '',
            TRUE,               # No 'undef' value used
            2, 6, 10, 11);
        $self->addLabel($grid, 'or xterm/RGB:',
            6, 7, 10, 11);
        my $entry5 = $self->addEntryWithIcon($grid, undef, \&status3Tab_checkEntry, undef, undef,
            7, 10, 10, 11);

        $self->addLabel($grid, '<i>Optional text</i>:',
            10, 12, 9, 10);
        my $entry6 = $self->addEntry($grid, undef, TRUE,
            10, 12, 10, 11,
            8, 16);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'gaugeFormatList',
            11, 7,
            $comboBox, $comboBox2, $comboBox3, $comboBox4, $comboBox5,
            $entry, $entry2, $entry3, $entry4, $entry5, $entry6,
        );
        $button->signal_connect('clicked' => sub {

            my ($var, $maxVar, $full, $empty, $label, $text, $flag);

            if ($self->checkEntryIcon($entry)) {
                $var = $entry->get_text();
            } else {
                $var = $comboBox->get_active_text();
            }

            if ($self->checkEntryIcon($entry2)) {
                $maxVar = $entry2->get_text();
            } else {
                $maxVar = $comboBox2->get_active_text();
            }

            if ($self->checkEntryIcon($entry3)) {
                $full = $entry3->get_text();
            } else {
                $full = $comboBox3->get_active_text();
            }

            if ($self->checkEntryIcon($entry4)) {
                $empty = $entry4->get_text();
            } else {
                $empty = $comboBox4->get_active_text();
            }

            if ($self->checkEntryIcon($entry5)) {
                $label = $entry5->get_text();
            } else {
                $label = $comboBox5->get_active_text();
            }

            if ($checkButton->get_active()) {
                $flag = TRUE;
            } else {
                $flag = FALSE;
            }

            $text = $entry6->get_text();

            if ($var && $maxVar && $full && $empty && $label) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV('gaugeFormatList',
                    undef, FALSE,
                    $var, $maxVar, $flag, $text, $full, $empty, $label,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'gaugeFormatList',
                );
                $self->resetEntryBoxes($entry, $entry2, $entry3, $entry4, $entry5, $entry6);
            }
        });

        # Tab complete
        return 1;
    }

    sub status3Tab_checkEntry {

        # Called by $self->status3Tab to check the text in the Gtk3::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk3::Entry
        #
        # Return values
        #   'undef' on improper arguments or if $text is invalid
        #   1 if $text is valid

        my ($self, $text, $check) = @_;

        # Check for improper arguments
        if (! defined $text || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->status3Tab_checkEntry',
                @_,
            );
        }

        # $text can be an xterm colour tag (in the range 'x0' to 'x255', or 'ux0' to 'ux255'; xterm
        #   tags are case-insensitive
        # $text can also be an RGB colour tag (in the range '#000000' to '#FFFFFF')
        if ($text =~ m/^u?x([0-9]+)$/i) {

            # (Don't permit 'x000005', but do permit 'x005' or 'x5')
            if ($1 >= 0 && $1 <= 255 && length ($1) <= 3) {
                return 1;
            } else {
                return undef;
            }

        } elsif ($text =~ m/^\#[0-9A-Fa-f]{6}$/) {

            return 1;

        } else {

            return undef;
        }
    }

    sub status4Tab {

        # Status4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Status task MSDP variables'],
        );

        # Status task MSDP variables
        $self->addLabel($grid, '<b>Status task MSDP variables</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>MSDP variables that should be converted directly into Status task variables</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'MSDP variable', 'text',
            'Status task variable', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'msdpStatusVarHash');

        # Add entries/comboboxes for adding new variables
        $self->addLabel($grid, 'MSDP variable',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Status task variable',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'msdpStatusVarHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($msdpVar, $var);

            $msdpVar = $entry->get_text();
            $var = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('msdpStatusVarHash', $msdpVar, $var);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'msdpStatusVarHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub status5Tab {

        # Status5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Status task MXP variables'],
        );

        # Status task MXP variables
        $self->addLabel($grid, '<b>Status task MXP variables</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>MXP entities whose values should be converted directly into Status task'
            . ' variables</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'MXP variable', 'text',
            'Status task variable', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'mxpStatusVarHash');

        # Add entries/comboboxes for adding new variables
        $self->addLabel($grid, 'MXP entity',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Status task variable',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'mxpStatusVarHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($mxpEnt, $var);

            $mxpEnt = $axmud::CLIENT->trimWhitespace($entry->get_text());
            $var = $axmud::CLIENT->trimWhitespace($entry2->get_text());

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('mxpStatusVarHash', $mxpEnt, $var);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'mxpStatusVarHash',
                );

                $self->resetEntryBoxes($entry, $entry2);

                # Update the Status task with the current value (if any) of the MXP entity
                if (
                    $self->session->statusTask
                    && $self->session->ivExists('mxpEntityHash', $mxpEnt)
                ) {
                    my $mxpObj = $self->session->ivShow('mxpEntityHash', $mxpEnt);
                    $self->session->statusTask->setValue($var, $mxpObj->value);
                }
            }
        });

        # Tab complete
        return 1;
    }

    sub status6Tab {

        # Status6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #   $taskObj        - A temporary Status task object
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $taskObj, $check) = @_;

        # Local variables
        my (
            $customString,
            @columnList, @comboList,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || ! defined $taskObj || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Status task - general patterns'],
        );

        # Status task - general patterns
        $self->addLabel($grid, '<b>Status task - general patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Patterns with group substrings, e.g. </i><b>(.*)</b> <i>and</i> <b>(\d+)</b><i>,'
            . ' one of which matches the data we need</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
            'Variable', 'text',
            'Mode', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'groupPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($grid, 'Substring #:',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            3, 6, 9, 10, 4, 4);

        $self->addLabel($grid, 'Mode:',
            9, 10, 9, 10);
        my $comboBox = $self->addComboBox($grid, undef, ['hide', 'show', 'choose'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        $self->addLabel($grid, 'Status task variable:',
            1, 6, 10, 11);

        $customString = 'Use a custom variable:';

        @comboList = sort {lc($a) cmp lc($b)} (
            $taskObj->ivKeys('constCharVarHash'),
            $taskObj->ivKeys('constFixedVarHash'),
            $taskObj->ivKeys('constPseudoVarHash'),
            $taskObj->ivKeys('constLocalVarHash'),
            $taskObj->ivKeys('constCounterVarHash'),
        );
        unshift (@comboList, $customString);
        my $comboBox2 = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 9, 10, 11);
        # ->signal_connect appears below

        my $entry3 = $self->addEntry($grid, undef, TRUE,
            9, 12, 10, 11);
        $comboBox2->signal_connect('changed' => sub {

            my $text = $comboBox2->get_active_text();
            if ($text eq $customString ) {
                $entry3->set_sensitive(TRUE);
            } else {
                $entry3->set_sensitive(FALSE);
                $entry3->set_text('');
            }
        });

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'groupPatternList',
            11, 4,
            $entry, $entry2, $entry3,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $var, $mode);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $mode = $comboBox->get_active_text();
            $var = $comboBox2->get_active_text();
            if ($var eq $customString) {

                $var = $entry3->get_text();
            }

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV('groupPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $var, $mode
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'groupPatternList',
                );
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub status7Tab {

        # Status7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status7Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Status task - bar patterns'],
        );

        # Status task - bar patterns
        $self->addLabel($grid, '<b>Status task - bar patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns containing data represented by multiple symbols</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
            'Variable', 'text',
            'Mode', 'text',
            'Unit', 'text',
            'Max units', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'barPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 6, 8, 9);

        $self->addLabel($grid, 'Unit:',
            6, 7, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            7, 9, 8, 9, 4, 4);

        $self->addLabel($grid, 'Max units:',
            9, 10, 8, 9);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            10, 12, 8, 9, 4, 4);

        $self->addLabel($grid, 'Substring #:',
            1, 2, 9, 10);
        my $entry4 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            2, 4, 9, 10, 4, 4);

        $self->addLabel($grid, 'Data type:',
            4, 6, 9, 10);

        @comboList = (
            'health', 'magic', 'energy', 'guild', 'social', 'xp_current', 'xp_next_level',
            'qp_current', 'qp_next_level', 'op_current', 'op_next_level',
        );

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 8, 9, 10);

        $self->addLabel($grid, 'Mode:',
            8, 10, 9, 10);
        my $comboBox2 = $self->addComboBox($grid, undef, ['hide', 'show', 'choose'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'barPatternList',
            10, 6,
            $entry, $entry2, $entry3, $entry4,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $var, $mode, $unit, $maxUnits);

            $pattern = $entry->get_text();
            $grpNum = $entry4->get_text();
            $var = $comboBox->get_active_text();
            $mode = $comboBox2->get_active_text();
            $unit = $entry2->get_text();
            $maxUnits = $entry3->get_text();

            if ($self->checkEntryIcon($entry, $entry2, $entry3, $entry4)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'barPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $var, $mode, $unit, $maxUnits,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'barPatternList');
                $self->resetEntryBoxes($entry, $entry2, $entry3, $entry4);
            }
        });

        # Tab complete
        return 1;
    }

    sub status8Tab {

        # Status8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status8Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Status task - character affect patterns'],
        );

        # Status task - character affect patterns
        $self->addLabel($grid, '<b>Status task - character affect patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Patterns containing data about spells (etc) affecting the character</i>',
            0, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
            'Display', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'affectPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 12, 8, 9);

        $self->addLabel($grid, 'Substring #:',
            1, 2, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            2, 6, 9, 10, 4, 4);

        $self->addLabel($grid, 'Mode:',
            9, 10, 9, 10);
        my $comboBox2 = $self->addComboBox($grid, undef, ['hide', 'show', 'choose'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'affectPatternList',
            10,  4,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $mode);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $mode = $comboBox2->get_active_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'affectPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $mode,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'affectPatternList',
                );
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub status9Tab {

        # Status9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status9Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _9',
            ['Status task - character stat patterns'],
        );

        # Status task - character stat patterns
        $self->addLabel($grid, '<b>Status task - character stat patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns containing data about the character\'s stats</i>',
            0, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
            'Stat', 'text',
            'Display', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'statPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 12, 8, 9);

        $self->addLabel($grid, 'Substring #:',
            1, 2, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            2, 6, 9, 10, 4, 4);

        $self->addLabel($grid, 'Stat:',
            6, 7, 9, 10);
        @comboList = $self->editObj->ivKeys('statHash');
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            7, 9, 9, 10);

        $self->addLabel($grid, 'Mode:',
            9, 10, 9, 10);
        my $comboBox2 = $self->addComboBox($grid, undef, ['hide', 'show', 'choose'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'statPatternList',
            10,  4,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $stat, $mode);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $stat = $comboBox->get_active_text();
            $mode = $comboBox2->get_active_text();

            if (! $stat) {

                $self->showMsgDialogue(
                    'Character stat error',
                    'error',
                    'No stat set (if the stat list is empty, you must create some stats in the'
                    . ' \'stats\' tab)',
                    'ok',
                );

            } elsif ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'statPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $stat, $mode,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'statPatternList');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub status10Tab {

        # Status10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status10Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 1_0',
            ['Status task ignore patterns', 'Quest complete patterns'],
        );

        # Status task ignore patterns
        $self->addLabel($grid, '<b>Status task ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns which should be ignored by the Status task</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'statusIgnorePatternList',
                'list', 'questCompletePatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'statusIgnorePatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Quest complete patterns
        $self->addLabel($grid, '<b>Quest complete patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Patterns announcing a quest is complete</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'questCompletePatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub status11Tab {

        # Status11 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status11Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 11',
            ['Status task age patterns', 'Status task time patterns'],
        );

        # Status task age patterns
        $self->addLabel($grid, '<b>Status task age patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns containing the character\'s age</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'agePatternList',
                'list', 'timePatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'agePatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Status task time patterns
        $self->addLabel($grid, '<b>Status task time patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Patterns containing the (in-game) time</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'timePatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub status12Tab {

        # Status12 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status12Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 12',
            ['Life patterns'],
        );

        # Life patterns
        $self->addLabel($grid, '<b>Life patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns seen when the character dies</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'deathPatternList',
                'list', 'resurrectPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'deathPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, '<i>Patterns seen when the character is resurrected</i>',
            1, 12, 6, 8);
        $self->addTextView($grid, 'resurrectPatternList', TRUE,
            1, 12, 8, 10,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub status13Tab {

        # Status13 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status13Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 13',
            ['Passing out patterns'],
        );

        # Passing out patterns
        $self->addLabel($grid, '<b>Passing out patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns seen when the character passes out</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'passedOutPatternList',
                'list', 'comeAroundPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'passedOutPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, '<i>Patterns seen when the character comes around</i>',
            1, 12, 6, 8);
        $self->addTextView($grid, 'comeAroundPatternList', TRUE,
            1, 12, 8, 10,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub status14Tab {

        # Status14 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->status14Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page 14',
            ['Sleep patterns'],
        );

        # Sleep patterns
        $self->addLabel($grid, '<b>Sleep patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns seen when the character falls asleep</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'fallAsleepPatternList',
                'list', 'wakeUpPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'fallAsleepPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel($grid, '<i>Patterns seen when the character wakes up</i>',
            1, 12, 6, 8);
        $self->addTextView($grid, 'wakeUpPatternList', TRUE,
            1, 12, 8, 10,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub inventoryTab {

        # Inventory tab
        #
        # 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 . '->inventoryTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Inventor_y');

        # Add tabs to the inner notebook
        $self->inventory1Tab($innerNotebook);
        $self->inventory2Tab($innerNotebook);
        $self->inventory3Tab($innerNotebook);
        $self->inventory4Tab($innerNotebook);
        $self->inventory5Tab($innerNotebook);
        $self->inventory6Tab($innerNotebook);
        $self->inventory7Tab($innerNotebook);

        return 1;
    }

    sub inventory1Tab {

        # Inventory1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventory1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Inventory collection mode'],
        );

        # Inventory collection mode
        $self->addLabel($grid, '<b>Inventory collection mode</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Sets the way in which the Inventory task interprets the character\'s inventory</i>',
            1, 12, 1, 2);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'match_all\'', 'inventoryMode',
            'match_all',    # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 12, 2, 3);
        $self->addLabel($grid, 'Only lines matching inventory task patterns (on Page 2) are used',
            2, 12, 3, 4);
        $self->addLabel(
            $grid,
            'The inventory is reset whenever a line matching a start/stop pattern is found',
            2, 12, 4, 5);
        $self->addLabel(
            $grid,
            '(so there must be at least one start pattern or one stop pattern)',
            3, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'start_stop\'', 'inventoryMode',
            'start_stop',
            TRUE,
            1, 12, 6, 7);
        $self->addLabel(
            $grid,
            'All lines between a line matching a \'start\' pattern and a line matching a \'stop\''
            . ' pattern are used',
            2, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'start_empty\'', 'inventoryMode',
            'start_empty',
            TRUE,
            1, 12, 8, 9);
        $self->addLabel(
            $grid,
            'All lines between a line matching a \'start\' pattern and the first empty line are'
            . ' used',
            2, 12, 9, 10);

        # Tab complete
        return 1;
    }

    sub inventory2Tab {

        # Inventory2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventory2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Inventory task patterns'],
        );

        # Inventory task patterns
        $self->addLabel($grid, '<b>Inventory task patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns containing data about the character\'s inventory</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
            'Type', 'text',
            'Mode', 'text',
            'Position', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'inventoryPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Type:',
            8, 9, 8, 9);

        @comboList = (
            'wield', 'hold', 'wear', 'carry',
            'sack',
            'purse', 'deposit', 'deposit_only', 'withdraw', 'withdraw_only', 'balance',
            'empty_purse', 'empty_bank',
            'misc',
            'ignore',
        );
        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 8, 9);

        $self->addLabel($grid, 'Substring #:',
            1, 2, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            2, 6, 9, 10, 4, 4);

        $self->addLabel($grid, 'Mode:',
            6, 7, 9, 10);
        my $comboBox2 = $self->addComboBox($grid, undef, ['hide', 'show'], '',
            TRUE,               # No 'undef' value used
            7, 9, 9, 10);

        $self->addLabel($grid, 'Position:',
            9, 10, 9, 10);
        my $comboBox3 = $self->addComboBox($grid, undef, ['start', 'stop', 'optional'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'inventoryPatternList',
            10,  5,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $type, $mode, $posn);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $type = $comboBox->get_active_text();
            $mode = $comboBox2->get_active_text();
            $posn = $comboBox3->get_active_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'inventoryPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $type, $mode, $posn,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'inventoryPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub inventory3Tab {

        # Inventory3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventory3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Inventory task ignore patterns'],
        );

        # Inventory task ignore patterns
        $self->addLabel($grid, '<b>Inventory task ignore patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns which should be ignored by the Inventory task</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Mode', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'inventoryIgnorePatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 10, 11);

        $self->addLabel($grid, 'Mode:',
            8, 10, 10, 11);
        my $comboBox = $self->addComboBox($grid, undef, ['hide', 'show'], '',
            TRUE,               # No 'undef' value used
            10, 12, 10, 11);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'inventoryIgnorePatternList',
            11,  2,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $mode);

            $pattern = $entry->get_text();
            $mode = $comboBox->get_active_text();

            if ($self->checkEntryIcon($entry)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'inventoryIgnorePatternList',
                    undef, FALSE,
                    $pattern, $mode,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'inventoryIgnorePatternList',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub inventory4Tab {

        # Inventory4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventory4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Inventory discard patterns', 'Inventory split patterns'],
        );

        # Inventory discard patterns
        $self->addLabel($grid, '<b>Inventory discard patterns</b>',
            0, 10, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns matching portions of an inventory line that should be discarded</i>',
            1, 10, 1, 2);
        $self->addRegexButton($grid,
            [
                'list', 'inventoryDiscardPatternList',
                'list', 'inventorySplitPatternList',
            ],
            10, 12, 0, 2);

        $self->addTextView($grid, 'inventoryDiscardPatternList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Inventory split patterns
        $self->addLabel($grid, '<b>Inventory split patterns</b>',
            0, 12, 6, 7);
        $self->addLabel($grid,
            '<i>Patterns matching portions of an inventory line between distinct objects</i>',
            1, 12, 7, 8);

        $self->addTextView($grid, 'inventorySplitPatternList', TRUE,
            1, 12, 8, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub inventory5Tab {

        # Inventory5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventory5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Condition task patterns'],
        );

        # Condition task patterns
        $self->addLabel($grid, '<b>Condition task patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns containing data about an inventory object\'s condition</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
            'Mode', 'text',
            'Condition', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'conditionPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 12, 8, 9);

        $self->addLabel($grid, 'Substring #:',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            3, 5, 9, 10, 4, 4);

        $self->addLabel($grid, 'Condition (0-100):',
            5, 7, 9, 10);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'int', 0, 100,
            7, 9, 9, 10, 3, 3);

        $self->addLabel($grid, 'Mode:',
            9, 10, 9, 10);
        my $comboBox = $self->addComboBox($grid, undef, ['hide', 'show'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'conditionPatternList',
            10,  4,
            $entry, $entry2, $entry3
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $mode, $condition);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $mode = $comboBox->get_active_text();
            $condition = $entry3->get_text();

            if ($self->checkEntryIcon($entry, $entry2, $entry3)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'conditionPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $mode, $condition,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'conditionPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2, $entry3);
            }
        });

        # Tab complete
        return 1;
    }

    sub inventory6Tab {

        # Inventory6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventory6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Condition task ignore patterns'],
        );

        # Condition task ignore patterns
        $self->addLabel($grid, '<b>Condition task ignore patterns</b>',
            0, 10, 0, 1);
        $self->addLabel($grid, '<i>Patterns which should be ignored by the Condition task</i>',
            1, 10, 1, 2);
        $self->addRegexButton($grid,
            [
                'list', 'conditionIgnorePatternList',
            ],
            10, 12, 0, 2);

        $self->addTextView($grid, 'conditionIgnorePatternList', TRUE,
            1, 12, 2, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub inventory7Tab {

        # Inventory7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventory7Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Inventory commands'],
        );

        # Inventory commands
        $self->addLabel($grid, '<b>Inventory commands</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Default list of commands sent to the world to update the Inventory task\'s'
            . ' variables (inherited by each</i>',
            1, 12, 1, 2);
        $self->addLabel($grid,
            '<i>new character profile - edit the current character profile to change the commands'
            . ' actually sent)</i>',
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Command', 'text',
            'How often to send', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'inventoryCmdHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Command',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Sent how often (in seconds):',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'inventoryCmdHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($cmd, $time);

            $cmd = $entry->get_text();
            $time = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('inventoryCmdHash', $cmd, $time);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'inventoryCmdHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub channelsTab {

        # Channels tab
        #
        # 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 . '->channelsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'C_hannels');

        # Add tabs to the inner notebook
        $self->channels1Tab($innerNotebook);
        $self->channels2Tab($innerNotebook);

        return 1;
    }

    sub channels1Tab {

        # Channels1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->channels1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Channel patterns'],
        );

        # Channel patterns
        $self->addLabel($grid, '<b>Channel patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of patterns used to divert text to the Channels and Divert tasks</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Channel', 'text',
            'Only in task window', 'bool',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 9);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'channelList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 12, 9, 10);

        $self->addLabel($grid, 'Channel name (1-16 chars):',
            1, 2, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            2, 6, 10, 11,
            16, 16);

        my $checkButton = $self->addCheckButton($grid, 'Only in task window?', undef, TRUE,
            7, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'channelList',
            11, 3,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $channel, $flag);

            $pattern = $entry->get_text();
            $channel = $entry2->get_text();
            $flag = $checkButton->get_active();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'channelList',
                    undef, FALSE,
                    $pattern, $channel, $flag,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'channelList',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # One non-standard button, sandwich in between the existing ones
        my $button2 = $self->addButton($grid,
            'Modify selected', 'Modifies the selected pattern\'s attributes', undef,
            3, 6, 11, 12);
        $button2->signal_connect('clicked' => sub {

            my (
                $index, $pattern, $channel, $flag, $choice, $choice2,
                @ivList, @comboList,
            );

            # Get the selected row
            ($index) = $slWidget->get_selected_indices();
            if (defined $index) {

                # Import the IV, and get the selected pattern's channel and flag
                @ivList = $self->getEditHash_listIV('channelList');
                $pattern = $ivList[$index * 3];
                $channel = $ivList[($index * 3) + 1];
                $flag = $ivList[($index * 3) + 2];
            }

            # Prompt the user for a new channel and/or flag setting
            @comboList = ('Gag text in \'main\' window', 'Display text in \'main\' window');
            ($choice, $choice2) = $self->showEntryComboDialogue(
                'Modify pattern attributes',
                'Enter a channel (1-16 chars)',
                undef,                              # No label above combo
                \@comboList,
                16,                                 # Max chars in entry box
            );

            if (defined $choice && $choice ne '' && (length $choice) <= 16) {

                # Update the list IV
                if ($choice2 eq $comboList[0]) {
                    splice(@ivList, ($index * 3), 3, $pattern, $choice, TRUE);
                } else {
                    splice(@ivList, ($index * 3), 3, $pattern, $choice, FALSE);
                }

                $self->ivAdd('editHash', 'channelList', \@ivList);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'channelList',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub channels2Tab {

        # Channels2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->channels2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Channel ignore patterns'],
        );

        # Channel ignore patterns
        $self->addLabel($grid, '<b>Channel ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which match text that should not be diverted to the Channels or Divert'
            . ' tasks</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noChannelList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noChannelList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub attackTab {

        # Attack tab
        #
        # 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 . '->attackTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Attac_k');

        # Add tabs to the inner notebook
        $self->attack1Tab($innerNotebook);
        $self->attack2Tab($innerNotebook);
        $self->attack3Tab($innerNotebook);
        $self->attack4Tab($innerNotebook);

        return 1;
    }

    sub attack1Tab {

        # Attack1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attack1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Target leaves patterns'],
        );

        # Target leaves patterns
        $self->addLabel($grid, '<b>Target leaves patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen when a target (or a potential target) leaves</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Target #', 'int',
            'Direction #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'targetLeavesPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            3, 6, 9, 10, 4, 4);

        $self->addLabel($grid, 'Direction substring # (0 if unspecified):',
            7, 9, 9, 10);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 9, 10, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'targetLeavesPatternList',
            10,  3,
            $entry, $entry2, $entry3
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $grpNum2);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $grpNum2 = $entry3->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'targetLeavesPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $grpNum2,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'targetLeavesPatternList',
                );
                $self->resetEntryBoxes($entry, $entry2, $entry3);
            }
        });

        # Tab complete
        return 1;
    }

    sub attack2Tab {

        # Attack2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attack2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Target arrives patterns'],
        );

        # Target arrives patterns
        $self->addLabel($grid, '<b>Target arrives patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns seen when a target (or a potential target) arrives</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Target #', 'int',
            'Direction #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'targetArrivesPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            3, 6, 9, 10, 4, 4);

        $self->addLabel($grid, 'Direction substring # (0 if unspecified):',
            7, 9, 9, 10);
        my $entry3 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 9, 10, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'targetArrivesPatternList',
            10,  3,
            $entry, $entry2, $entry3
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum, $grpNum2);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();
            $grpNum2 = $entry3->get_text();

            if ($self->checkEntryIcon($entry, $entry2, $entry3)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'targetArrivesPatternList',
                    undef, FALSE,
                    $pattern, $grpNum, $grpNum2,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'targetArrivesPatternList',
                );
                $self->resetEntryBoxes($entry, $entry2, $entry3);
            }
        });

        # Tab complete
        return 1;
    }

    sub attack3Tab {

        # Attack3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attack3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Target leaves ignore patterns', 'Target arrives ignore patterns'],
        );

        # Target leaves ignore patterns
        $self->addLabel($grid, '<b>Target leaves ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean a target (or potential target) didn\'t just leave</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noTargetLeavesPatternList',
                'list', 'noTargetArrivesPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noTargetLeavesPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Target arrives ignore patterns
        $self->addLabel($grid, '<b>Target arrives ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean a target (or potential target) didn\'t just arrive</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noTargetArrivesPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub attack4Tab {

        # Attack4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attack4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['No fights room patterns', 'No interactions room patterns'],
        );

        # No fights room patterns
        $self->addLabel($grid, '<b>No fights room patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns seen when the character initiates a fight in a room which forbids'
            . ' them</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noFightsRoomPatternList',
                'list', 'noInteractionsRoomPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noFightsRoomPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # No interactions room patterns
        $self->addLabel($grid, '<b>No interactions room patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns seen when the character initiates an interaction in a room which forbids'
            . ' them</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noInteractionsRoomPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldFightTab {

        # WorldFight tab
        #
        # 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 . '->worldFightTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Fight');

        # Add tabs to the inner notebook
        $self->worldFight1Tab($innerNotebook);
        $self->worldFight2Tab($innerNotebook);
        $self->worldFight3Tab($innerNotebook);
        $self->worldFight4Tab($innerNotebook);
        $self->worldFight5Tab($innerNotebook);
        $self->worldFight6Tab($innerNotebook);
        $self->worldFight7Tab($innerNotebook);
        $self->worldFight8Tab($innerNotebook);
        $self->worldFight9Tab($innerNotebook);

        return 1;
    }

    sub worldFight1Tab {

        # WorldFight1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Fight started patterns'],
        );

        # Fight started patterns
        $self->addLabel($grid, '<b>Fight started patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen when the character initiates a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'fightStartedPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'fightStartedPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'fightStartedPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'fightStartedPatternList',
                );
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldFight2Tab {

        # WorldFight2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Cannot find target patterns'],
        );

        # Cannot find target patterns
        $self->addLabel($grid, '<b>Cannot find target patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns seen when the character initiates a fight with a non-existent target</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'cannotFindTargetPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'cannotFindTargetPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'cannotFindTargetPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'cannotFindTargetPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldFight3Tab {

        # WorldFight3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Fight started ignore patterns', 'Target not found ignore patterns'],
        );

        # Fight started ignore patterns
        $self->addLabel($grid, '<b>Fight started ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns which mean the character didn\'t initiate a fight</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noFightStartedPatternList',
                'list', 'noCannotFindTargetPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noFightStartedPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Target not found ignore patterns
        $self->addLabel($grid, '<b>Target not found ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean the character couldn\'t fight a non-existent target</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noCannotFindTargetPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldFight4Tab {

        # WorldFight4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Target already dead patterns'],
        );

        # Target already dead patterns
        $self->addLabel($grid, '<b>Target already dead patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns seen when the character initiates a fight with a dead target</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'targetAlreadyDeadPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'targetAlreadyDeadPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'targetAlreadyDeadPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'targetAlreadyDeadPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldFight5Tab {

        # WorldFight5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Target killed patterns'],
        );

        # Target killed patterns
        $self->addLabel($grid, '<b>Target killed patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen when the character wins a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'targetKilledPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'targetKilledPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'targetKilledPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'targetKilledPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldFight6Tab {

        # WorldFight6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Target already dead ignore patterns', 'Target killed ignore patterns'],
        );

        # Target already dead ignore patterns
        $self->addLabel($grid, '<b>Target already dead ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns which mean the character didn\'t fight a corpse</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noTargetAlreadyDeadPatternList',
                'list', 'noTargetKilledPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noTargetAlreadyDeadPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Target killed ignore patterns
        $self->addLabel($grid, '<b>Target killed ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Patterns which mean the character didn\'t just win a fight</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noTargetKilledPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldFight7Tab {

        # WorldFight7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight7Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Target defeat patterns'],
        );

        # Target defeat patterns
        $self->addLabel($grid, '<b>Target defeat patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns seen when the character loses a fight (besides getting killed/knocked'
            . ' out)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'fightDefeatPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'fightDefeatPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'fightDefeatPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'fightDefeatPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldFight8Tab {

        # WorldFight8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight8Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Remote wimpy engaged patterns'],
        );

        # Remote wimpy engaged patterns
        $self->addLabel($grid, '<b>Remote wimpy engaged patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Patterns seen when the world engages wimpy mode (presumably ending a fight)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV($slWidget, scalar (@columnList / 2), 'wimpyEngagedPatternList');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'wimpyEngagedPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'wimpyEngagedPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'wimpyEngagedPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldFight9Tab {

        # WorldFight9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldFight9Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _9',
            ['Target defeat ignore patterns', 'Remote wimpy engaged ignore patterns'],
        );

        # Target defeat ignore patterns
        $self->addLabel($grid, '<b>Target defeat ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns which mean the character didn\'t lose a fight</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noFightDefeatPatternList',
                'list', 'noWimpyEngagedPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noFightDefeatPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Remote wimpy engaged ignore patterns
        $self->addLabel($grid, '<b>Remote wimpy engaged ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Patterns which mean the world didn\'t engage wimpy mode</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noWimpyEngagedPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldInteractionTab {

        # WorldInteraction tab
        #
        # 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 . '->worldInteractionTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'Int_eraction');

        # Add tabs to the inner notebook
        $self->worldInteraction1Tab($innerNotebook);
        $self->worldInteraction2Tab($innerNotebook);
        $self->worldInteraction3Tab($innerNotebook);
        $self->worldInteraction4Tab($innerNotebook);
        $self->worldInteraction5Tab($innerNotebook);
        $self->worldInteraction6Tab($innerNotebook);
        $self->worldInteraction7Tab($innerNotebook);
        $self->worldInteraction8Tab($innerNotebook);
        $self->worldInteraction9Tab($innerNotebook);

        return 1;
    }

    sub worldInteraction1Tab {

        # WorldInteraction1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Interaction started patterns'],
        );

        # Interaction started patterns
        $self->addLabel($grid, '<b>Interaction started patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen when the character initiates an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'interactionStartedPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'interactionStartedPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'interactionStartedPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionStartedPatternList',
                );
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldInteraction2Tab {

        # WorldInteraction2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Cannot interact patterns'],
        );

        # Cannot interact patterns
        $self->addLabel($grid, '<b>Cannot interact patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns seen when the character fails to initiate an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'cannotInteractPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'cannotInteractPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'cannotInteractPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'cannotInteractPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldInteraction3Tab {

        # WorldInteraction3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Interaction started ignore patterns', 'Cannot interact ignore patterns'],
        );

        # Interaction started ignore patterns
        $self->addLabel($grid, '<b>Interaction started ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean the character didn\'t initiate an interaction</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noInteractionStartedPatternList',
                'list', 'noCannotInteractPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noInteractionStartedPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Cannot interact ignore patterns
        $self->addLabel($grid, '<b>Cannot interact ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean the character didn\'t fail to initiate an interaction</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noCannotInteractPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldInteraction4Tab {

        # WorldInteraction4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Interaction success patterns'],
        );

        # Interaction success patterns
        $self->addLabel($grid, '<b>Interaction success patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen when the character wins an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'interactionSuccessPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'interactionSuccessPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'interactionSuccessPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionSuccessPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldInteraction5Tab {

        # WorldInteraction5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Interaction fail patterns'],
        );

        # Interaction fail patterns
        $self->addLabel($grid, '<b>Interaction fail patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen when the character loses an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'interactionFailPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'interactionFailPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'interactionFailPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionFailPatternList',
                );
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldInteraction6Tab {

        # WorldInteraction6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Interaction success ignore patterns', 'Interaction fail ignore patterns'],
        );

        # Interaction success ignore patterns
        $self->addLabel($grid, '<b>Interaction success ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns seen when the character didn\'t just win an interaction</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noInteractionSuccessPatternList',
                'list', 'noInteractionFailPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noInteractionSuccessPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Interaction fail ignore patterns
        $self->addLabel($grid, '<b>Interaction fail ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns seen when the character didn\'t just lose an interaction</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noInteractionFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldInteraction7Tab {

        # WorldInteraction7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction7Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            ['Interaction conversion patterns'],
        );

        # Interaction conversion patterns
        $self->addLabel($grid, '<b>Interaction conversion patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Patterns seen when an interaction turns into a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'interactionFightPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'interactionFightPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'interactionFightPatternList',
                    undef, FALSE,
                    $pattern, $grpNum
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionFightPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldInteraction8Tab {

        # WorldInteraction8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction8Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Interaction disaster patterns'],
        );

        # Interaction disaster patterns
        $self->addLabel($grid, '<b>Interaction disaster patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Patterns seen when an interaction requires the character to flee</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Substring #', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_listIV(
            $slWidget,
            scalar (@columnList / 2),
            'interactionDisasterPatternList',
        );

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Target substring #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $grid,
            $slWidget,
            'interactionDisasterPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $grpNum);

            $pattern = $entry->get_text();
            $grpNum = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add new values to (the end of) the list IV
                $self->addEditHash_listIV(
                    'interactionDisasterPatternList',
                    undef, FALSE,
                    $pattern, $grpNum,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionDisasterPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub worldInteraction9Tab {

        # WorldInteraction9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldInteraction9Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _9',
            ['Interaction conversion ignore patterns', 'Interaction disaster ignore patterns'],
        );

        # Interaction conversion ignore patterns
        $self->addLabel($grid, '<b>Interaction conversion ignore patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns seen when an interaction didn\'t just turn into a fight</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'noInteractionFightPatternList',
                'list', 'noInteractionDisasterPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'noInteractionFightPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Interaction disaster ignore patterns
        $self->addLabel($grid, '<b>Interaction disaster ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns seen when an interaction didn\'t just require the character to flee</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'noInteractionDisasterPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldCommandsTab {

        # WorldCommands tab
        #
        # 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 . '->worldCommandsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Commands');

        # Add tabs to the inner notebook
        $self->worldCommands1Tab($innerNotebook);
        $self->worldCommands2Tab($innerNotebook);
        $self->worldCommands3Tab($innerNotebook);
        $self->worldCommands4Tab($innerNotebook);
        $self->worldCommands5Tab($innerNotebook);
        $self->worldCommands6Tab($innerNotebook);

        return 1;
    }

    sub worldCommands1Tab {

        # WorldCommands1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldCommands1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Get success patterns', 'Get heavy patterns'],
        );

        # Get success patterns
        $self->addLabel($grid, '<b>Get success patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'get axe\' was successful</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'getSuccessPatternList',
                'list', 'getHeavyPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'getSuccessPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Get heavy patterns
        $self->addLabel($grid, '<b>Get heavy patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'get axe\' failed because the object was too'
            . ' heavy</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'getHeavyPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldCommands2Tab {

        # WorldCommands2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldCommands2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Get fail patterns', 'Drop success patterns'],
        );

        # Get fail patterns
        $self->addLabel($grid, '<b>Get fail patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'get axe\' failed because there is such object'
            . ' to get</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'getFailPatternList',
                'list', 'dropSuccessPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'getFailPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Drop success patterns
        $self->addLabel($grid, '<b>Drop success patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'drop axe\' was successful</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'dropSuccessPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldCommands3Tab {

        # WorldCommands3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldCommands3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Drop forbid patterns', 'Drop fail patterns'],
        );

        # Drop forbid patterns
        $self->addLabel($grid, '<b>Drop forbid patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'drop axe\' failed because the world forbids'
            . ' it</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'dropForbidPatternList',
                'list', 'dropFailPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'dropForbidPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Drop fail patterns
        $self->addLabel($grid, '<b>Drop fail patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'drop axe\' failed because there is no such'
            . ' object to drop</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'dropFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldCommands4Tab {

        # WorldCommands4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldCommands4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Buy success patterns', 'Buy partial success patterns'],
        );

        # Buy success patterns
        $self->addLabel($grid, '<b>Buy success patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'buy axe\' was successful</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'buySuccessPatternList',
                'list', 'buyPartialPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'buySuccessPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Buy partial success patterns
        $self->addLabel($grid, '<b>Buy partial success patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'buy ten axes\' partially succeeded'
            . ' (e.g. bought five)</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'buyPartialPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldCommands5Tab {

        # WorldCommands5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldCommands5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Buy fail patterns', 'Sell success patterns'],
        );

        # Buy fail patterns
        $self->addLabel($grid, '<b>Buy fail patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'buy axe\' failed because there is no such'
            . ' object to buy</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'buyFailPatternList',
                'list', 'sellSuccessPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'buyFailPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Sell success patterns
        $self->addLabel($grid, '<b>Sell success patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'sell axe\' was successful</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'sellSuccessPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub worldCommands6Tab {

        # WorldCommands6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->worldCommands6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Sell partial success patterns', 'Sell fail patterns'],
        );

        # Sell partial success patterns
        $self->addLabel($grid, '<b>Sell partial success patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'sell ten axes\' partially succeeded (e.g.'
            . ' sold five)</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'sellPartialPatternList',
                'list', 'sellFailPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'sellPartialPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Sell fail patterns
        $self->addLabel($grid, '<b>Sell fail patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns which mean a command like \'sell axe\' failed because there is no such'
            . ' object to sell</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'sellFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub advanceTab {

        # Advance tab
        #
        # 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 . '->advanceTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Advance',
            ['Advance success patterns', 'Advance fail patterns'],
        );

        # Advance success patterns
        $self->addLabel($grid, '<b>Advance success patterns</b>',
            0, 10, 0, 2);
        $self->addLabel($grid, '<i>Patterns seen when the character advances their skills</i>',
            1, 10, 2, 4);
        $self->addRegexButton($grid,
            [
                'list', 'advanceSuccessPatternList',
                'list', 'advanceFailPatternList',
            ],
            10, 12, 0, 4);

        $self->addTextView($grid, 'advanceSuccessPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Advance fail patterns
        $self->addLabel($grid, '<b>Advance fail patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Patterns seen when the character fails to advance their skills</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'advanceFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub currencyTab {

        # Currency tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->currencyTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'C_urrency',
            ['Currency conversion chart'],
        );

        # Currency conversion chart
        $self->addLabel($grid, '<b>Currency conversion chart</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Conversion into the standard unit:',
            1, 6, 1, 2);
        @comboList = $self->editObj->ivKeys('currencyHash');
        my $comboBox = $self->addComboBox($grid, 'standardCurrencyUnit', \@comboList, '',
            FALSE,              # 'undef' value allowed
            6, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Currency unit', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'currencyHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Currency unit:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Value (in standard units):',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', 0, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'currencyHash',
            9,
            $entry, $entry2,
        );

        $button->signal_connect('clicked' => sub {

            my ($unit, $value);

            $unit = $entry->get_text();
            $value = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('currencyHash', $unit, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'currencyHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        $self->addLabel($grid,
            'Round cash values to this many decimal places (0-9, or -1 to prevent rounding)',
            1, 9, 10, 11);
        $self->addEntryWithIcon($grid, 'currencyRounding', 'int', -1, 9,
            9, 12, 10, 11);

        # Tab complete
        return 1;
    }

    sub missionsTab {

        # World Missions tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->missionsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Missions',
            ['Missions list'],
        );

        # Missions list
        $self->addLabel($grid, '<b>Missions list</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of missions - linear scripts for simple sequences of commands</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Mission name', 'text',
            'Description', 'text',
            'Strings', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 9);

        # Initialise the list
        $self->missionsTab_refreshList($slWidget, (scalar @columnList / 2));

        # Add some buttons and a single entry box
        $self->addLabel($grid, 'Name',
            1, 2, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 3, 16,
            2, 4, 9, 10);
        my $button = $self->addButton($grid,
            'Add...', 'Add a new mission using the contents of the current recording', undef,
            4, 6, 9, 10,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($missionName, $childWinObj);

            if ($self->checkEntryIcon($entry)) {

                $missionName = $entry->get_text();

                # Check the mission doesn't already exist
                if ($self->editObj->ivExists('missionHash', $missionName)) {

                    # Empty the entry box
                    $entry->set_text('');

                } else {

                    # Create a new mission
                    $self->session->pseudoCmd(
                        'addmission ' . $missionName,
                        $self->pseudoCmdMode,
                    );

                    # If the mission was created...
                    if ($self->editObj->ivExists('missionHash', $missionName)) {

                        # Open a mission 'edit' window for the user to customise the mission
                        $childWinObj = $self->createFreeWin(
                            'Games::Axmud::EditWin::Mission',
                            $self,
                            $self->session,
                            'Edit mission \'' . $missionName . '\'',
                            $self->editObj->ivShow('missionHash', $missionName),
                            FALSE,                  # Not temporary
                        );

                        if ($childWinObj) {

                            # When the 'edit' window closes, update widgets and/or IVs
                            $self->add_childDestroy(
                                $childWinObj,
                                'missionsTab_refreshList',
                                [$slWidget, (scalar @columnList / 2)],
                            );
                        }

                        # Reset the list to show the mission (immediately)
                        $self->missionsTab_refreshList($slWidget, (scalar @columnList / 2));
                    }
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Edit...', 'Edit the selected mission', undef,
            6, 8, 9, 10);
        $button2->signal_connect('clicked' => sub {

            my ($missionName, $childWinObj);

            ($missionName) = $self->getSimpleListData($slWidget, 0);
            if (defined $missionName && $self->editObj->ivExists('missionHash', $missionName)) {

                # Open a mission 'edit' window
                $self->createFreeWin(
                    'Games::Axmud::EditWin::Mission',
                    $self,
                    $self->session,
                    'Edit mission \'' . $missionName . '\'',
                    $self->editObj->ivShow('missionHash', $missionName),
                    FALSE,                  # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'missionsTab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }
            }
        });

        my $button3 = $self->addButton($grid, 'Delete', 'Delete the selected mission', undef,
            8, 10, 9, 10,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($missionName) = $self->getSimpleListData($slWidget, 0);
            if (defined $missionName) {

                # Delete the mission
                $self->session->pseudoCmd(
                    'deletemission ' . $missionName,
                    $self->pseudoCmdMode,
                );

                # Reset the list to show the mission
                $self->missionsTab_refreshList($slWidget, (scalar @columnList / 2));
            }
        });

        my $button4 = $self->addButton($grid,
            'Dump', 'Display the list of missions in the \'main\' window', undef,
            10, 12, 9, 10);
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listmission', $self->pseudoCmdMode);
        });

        my $button5 = $self->addButton($grid,
            'Refresh list', 'Refresh the list of missions', undef,
            1, 4, 10, 11);
        $button5->signal_connect('clicked' => sub {

            $self->missionsTab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button6 = $self->addButton($grid,
            'Start', 'Starts the selected mission', undef,
            8, 10, 10, 11,
            TRUE,           # Irreversible
        );
        $button6->signal_connect('clicked' => sub {

            my ($missionName) = $self->getSimpleListData($slWidget, 0);
            if (
                defined $missionName
                && $self->editObj->ivExists('missionHash', $missionName)
            ) {
                # Start the mission
                $self->session->pseudoCmd(
                    'startmission ' . $missionName,
                    $self->pseudoCmdMode,
                );
            }
        });

        my $button7 = $self->addButton($grid,
            'Start slowly', 'Starts the selected mission incrementally', undef,
            10, 12, 10, 11,
            TRUE,           # Irreversible
        );
        $button7->signal_connect('clicked' => sub {

            my ($missionName) = $self->getSimpleListData($slWidget, 0);
            if (defined $missionName) {

                # Start the mission
                $self->session->pseudoCmd(
                    'startmission ' . $missionName . ' -i',
                    $self->pseudoCmdMode,
                );
            }
        });

        # Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $entry->set_sensitive(FALSE);
            $button->set_sensitive(FALSE);
            $button2->set_sensitive(FALSE);
            $button3->set_sensitive(FALSE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(FALSE);
            $button6->set_sensitive(FALSE);
            $button7->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub missionsTab_refreshList {

        # Resets the simple list displayed by $self->missionsTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@missionList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->missionsTab_refreshList',
                @_,
            );
        }

        # Get a sorted list of missions
        @missionList
            = sort {lc($a->name) cmp lc($b->name)} ($self->editObj->ivValues('missionHash'));

        # Compile the simple list data
        foreach my $missionObj (@missionList) {

            push (@dataList,
                $missionObj->name,
                $missionObj->descrip,
                scalar $missionObj->missionList,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub questsTab {

        # Quests tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->questsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Quests',
            ['Quest list'],
        );

        # Quest list
        $self->addLabel($grid, '<b>Quest list</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Number of quests listed',
            1, 4, 1, 2);
        my $entry = $self->addEntry($grid, 'questCount', FALSE,
            4, 6, 1, 2, 4, 4);
        $self->addLabel($grid, 'Total quest points',
            7, 10, 1, 2);
        my $entry2 = $self->addEntry($grid, 'questPointCount', FALSE,
            10, 12, 1, 2, 8, 8);

        $self->addLabel($grid, 'Total quest XP',
            1, 4, 2, 3);
        my $entry3 = $self->addEntry($grid, 'questXPCount', FALSE,
            4, 6, 2, 3, 16, 16);
        $self->addLabel($grid, 'Total quest cash',
            7, 10, 2, 3);
        my $entry4 = $self->addEntry($grid, 'questCashCount', FALSE,
            10, 12, 2, 3, 16, 16);

        # Add a simple list
        @columnList = (
            'Quest name', 'text',
            'Mission?', 'text',
            'Solution?', 'bool',
            'Comments?', 'bool',
            'Points', 'text',
            'XP', 'text',
            'Cash', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 10);
        # Initialise the list
        $self->questsTab_refreshList($slWidget, (scalar @columnList / 2));

        # Add some buttons and a single entry box
        $self->addLabel($grid, 'Name',
            1, 2, 10, 11);
        my $entry5 = $self->addEntryWithIcon($grid, undef, 'string', 3, 16,
            2, 4, 10, 11);
        my $button = $self->addButton($grid, 'Add...', 'Add a new quest', undef,
            4, 6, 10, 11,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($questName, $childWinObj);

            if ($self->checkEntryIcon($entry5)) {

                $questName = $entry5->get_text();

                # Check the quest doesn't already exist
                if ($self->editObj->ivExists('questHash', $questName)) {

                    # Empty the entry box
                    $entry5->set_text('');

                } else {

                    # Create a new quest
                    $self->session->pseudoCmd(
                        'addquest ' . $questName,
                        $self->pseudoCmdMode,
                    );

                    # If the quest was created...
                    if ($self->editObj->ivExists('questHash', $questName)) {

                        # Open a quest 'edit' window for the user to customise the quest
                        $childWinObj = $self->createFreeWin(
                            'Games::Axmud::EditWin::Quest',
                            $self,
                            $self->session,
                            'Edit quest \'' . $questName . '\'',
                            $self->editObj->ivShow('questHash', $questName),
                            FALSE,                  # Not temporary
                        );

                        if ($childWinObj) {

                            # When the 'edit' window closes, update widgets and/or IVs
                            $self->add_childDestroy(
                                $childWinObj,
                                'questsTab_refreshList',
                                [$slWidget, (scalar @columnList / 2)],
                            );

                            $self->add_childDestroy(
                                $childWinObj,
                                'questsTab_refreshEntries',
                                [$entry, $entry2, $entry3, $entry4],
                            );
                        }

                        # Refresh the simple list (immediately)
                        $self->questsTab_refreshList($slWidget, (scalar @columnList / 2));
                        # Refresh the desensitised entry boxes (immediately)
                        $self->questsTab_refreshEntries($entry, $entry2, $entry3, $entry4);

                        # Empty the sensitised entry box (immediately)
                        $self->resetEntryBoxes($entry5);
                    }
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Edit...', 'Edit the selected quest', undef,
            6, 8, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my ($questName, $childWinObj);

            ($questName) = $self->getSimpleListData($slWidget, 0);
            if (defined $questName && $self->editObj->ivExists('questHash', $questName)) {

                # Open a quest 'edit' window
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::Quest',
                    $self,
                    $self->session,
                    'Edit quest \'' . $questName . '\'',
                    $self->editObj->ivShow('questHash', $questName),
                    FALSE,                  # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'questsTab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );

                    $self->add_childDestroy(
                        $childWinObj,
                        'questsTab_refreshEntries',
                        [$entry, $entry2, $entry3, $entry4],
                    );
                }
            }
        });

        my $button3 = $self->addButton($grid, 'Delete', 'Delete the selected quest', undef,
            8, 10, 10, 11,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($questName) = $self->getSimpleListData($slWidget, 0);
            if (defined $questName && $self->editObj->ivExists('questHash', $questName)) {

                # Delete the quest
                $self->session->pseudoCmd(
                    'deletequest ' . $questName,
                    $self->pseudoCmdMode,
                );

                # Refresh the simple list
                $self->questsTab_refreshList($slWidget, (scalar @columnList / 2));
                # Refresh the desensitised entry boxes
                $self->questsTab_refreshEntries($entry, $entry2, $entry3, $entry4);
            }
        });

        my $button4 = $self->addButton($grid,
            'Dump', 'Display the list of quests in the \'main\' window', undef,
            10, 12, 10, 11);
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listquest', $self->pseudoCmdMode);

            # Refresh the simple list
            $self->questsTab_refreshList($slWidget, (scalar @columnList / 2));
            # Refresh the desensitised entry boxes
            $self->questsTab_refreshEntries($entry, $entry2, $entry3, $entry4);
        });

        my $button5 = $self->addButton($grid, 'Refresh list', 'Refresh the list of quests', undef,
            10, 12, 11, 12);
        $button5->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->questsTab_refreshList($slWidget, (scalar @columnList / 2));
            # Refresh the desensitised entry boxes
            $self->questsTab_refreshEntries($entry, $entry2, $entry3, $entry4);
        });

        # Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $entry5->set_sensitive(FALSE);
            $button->set_sensitive(FALSE);
            $button2->set_sensitive(FALSE);
            $button3->set_sensitive(FALSE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub questsTab_refreshList {

        # Resets the simple list displayed by $self->questsTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @sortedList, @dataList,
            %questHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->questsTab_refreshList',
                @_,
            );
        }

        # Import the IV
        %questHash = $self->getEditHash_hashIV('questHash');
        # Get a sorted list of quests
        @sortedList = sort {lc($a->name) cmp lc($b->name)} (values %questHash);

        # Compile the simple list data
        foreach my $questObj (@sortedList) {

            push (@dataList,
                $questObj->name,
                $questObj->missionName,
                scalar $questObj->solutionList,
                scalar $questObj->commentList,
                $questObj->questPoints,
                $questObj->questXP,
                $questObj->questCash,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub questsTab_refreshEntries {

        # Refreshes the values displayed in the Gtk3::Entry boxes displayed by $self->questsTab
        #
        # Expected arguments
        #   $entry, $entry2, $entry3, $entry4
        #       - The entry boxes to refresh
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $entry, $entry2, $entry3, $entry4, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $entry || ! defined $entry2 || ! defined $entry3 || ! defined $entry4
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->questsTab_refreshEntries',
                @_,
            );
        }

        # (The values stored in these IVs are set by ';addquest', ';deletequest', etc, so there's
        #   no need to refer to $self->editHash)
        $entry->set_text($self->editObj->questCount);
        $entry2->set_text($self->editObj->questPointCount);
        $entry3->set_text($self->editObj->questXPCount);
        $entry4->set_text($self->editObj->questCashCount);

        return 1;
    }

    sub statsTab {

        # Stats tab
        #
        # 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 . '->statsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Stats');

        # Add tabs to the inner notebook
        $self->stats1Tab($innerNotebook);
        $self->stats2Tab($innerNotebook);

        return 1;
    }

    sub stats1Tab {

        # Stats1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->stats1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Character stats (1/2)'],
        );

        # Character stats (1/2)
        $self->addLabel($grid, '<b>Character stats (1/2)</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of character stats for this world and their default values</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Stat', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Stat',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Default value (usually 0):',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'statHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($unit, $value);

            $unit = $entry->get_text();
            $value = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('statHash', $unit, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub stats2Tab {

        # Stats2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->stats2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Character stats (2/2)'],
        );

        # Character stats (2/2)
        $self->addLabel($grid, '<b>Character stats (2/2)</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $grid,
            '<i>Optional list of stats - if set, the Status task displays stats in this order</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'statOrderList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    # Support functions

    sub ipv4Check {

        # Called by $self->settings1Tab when user enters an IPV4 address
        # Passes the call on to GA::Client to check the validity of the address, and returns the
        #   result
        #
        # Expected arguments
        #   $value  - The value to check
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns the result of the call to GA::Client->ipv4Check

        my ($self, $value, $check) = @_;

        # Check for improper arguments
        if (! defined $value || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->ipv4Check', @_);
        }

        return $axmud::CLIENT->ipv4Check($value);
    }

    sub ipv6Check {

        # Called by $self->settings1Tab when user enters an IPV6 address
        # Passes the call on to GA::Client to check the validity of the address, and returns the
        #   result
        #
        # Expected arguments
        #   $value  - The value to check
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns the result of the call to GA::Client->ipv6Check

        my ($self, $value, $check) = @_;

        # Check for improper arguments
        if (! defined $value || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->ipv6Check', @_);
        }

        return $axmud::CLIENT->ipv6Check($value);
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    sub comboHash
        { my $self = shift; return %{$self->{comboHash}}; }
}

{ package Games::Axmud::EditWin::Profile::Guild;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Profile Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Profile::Guild')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of profile is being
        #   edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # If this is a current profile, set a flag
        if ($self->session->currentGuild && $self->session->currentGuild eq $self->editObj) {

            $self->ivPoke('currentFlag', TRUE);
        }

        $self->initialTab();        # Inherited from GA::EditWin::Generic::Profile
        $self->notesTab();          # Inherited from GA::EditWin::Generic::Profile
        $self->commandsTab();       # Inherited from GA::EditWin::Generic::Profile
        $self->fightTab();          # Inherited from GA::EditWin::Generic::Profile
        $self->interactionTab();    # Inherited from GA::EditWin::Generic::Profile
        $self->skillsTab();
        $self->privateDataTab(      # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private data hash',
            'Data created by your own plugins and scripts, but stored in this profile',
        );

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub skillsTab {

        # Skills tab
        #
        # 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 . '->skillsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Skills');

        # Add tabs to the inner notebook
        $self->skills1Tab($innerNotebook);
        $self->skills2Tab($innerNotebook);

        return 1;
    }

    sub skills1Tab {

        # Skills1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->skills1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['List of guild skills'],
        );

        # List of guild skills
        $self->addLabel($grid, '<b>List of guild skills</b>',
            0, 10, 0, 1);
        my $button = $self->addButton($grid,
            'Dump lists', 'Display these lists in the \'main\' window', undef,
            10, 12, 0, 1);
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listadvance -g', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, '<i>All skills available to this guild</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Skill', 'text',
            'Maximum level', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'skillMaxLevelHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Skill:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Max level:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
            9, 12, 8, 9);

        # Add buttons at various positions
        my $button2 = Gtk3::Button->new('Add');
        $button2->signal_connect('clicked' => sub {

            my ($skill, $maxLevel);

            $skill = $entry->get_text();
            $maxLevel = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('skillMaxLevelHash', $skill, $maxLevel);

                # All the keys in the hash 'skillMaxLevelHash' must be saved as a list in a
                #   second IV
                $self->storeListColumnInList($slWidget, 'skillList', 0);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'skillMaxLevelHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });
        $button->set_tooltip_text('Add a new pattern with the above settings');
        $grid->attach($button2, 1, 9, 2, 1);

        my $button3 = Gtk3::Button->new('Delete');
        $button3->signal_connect('clicked' => sub {

            my ($key) = $self->getSimpleListData($slWidget, 0);
            if (defined $key) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('skillMaxLevelHash', $key, undef, TRUE);

                # All the keys in the hash 'skillMaxLevelHash' must be saved as a list in a second
                #   IV
                $self->storeListColumnInList($slWidget, 'skillList', 0);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'skillMaxLevelHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });
        $button3->set_tooltip_text('Delete the selected pattern');
        $grid->attach($button3, 6, 9, 2, 1);

        my $button4 = Gtk3::Button->new('Reset');
        $button4->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('skillMaxLevelHash', 'skillMaxLevelHash');

            # All the keys in the hash 'skillMaxLevelHash' must be saved as a list in a second IV
            $self->storeListColumnInList($slWidget, 'skillList', 0);

            # Refresh the simple list and reset entry boxes
            $self->refreshList_hashIV($slWidget, 2, 'skillMaxLevelHash');
            $self->resetEntryBoxes($entry, $entry2);
        });
        $button4->set_tooltip_text('Reset the list of patterns');
        $grid->attach($button4, 8, 9, 2, 1);

        my $button5 = Gtk3::Button->new('Clear');
        $button5->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'skillMaxLevelHash', {});

            # All the keys in the hash 'skillMaxLevelHash' must be saved as a list in a second IV
            $self->storeListColumnInList($slWidget, 'skillList', 0);

            # Refresh the simple list and reset entry boxes
            $self->refreshList_hashIV($slWidget, 2, 'skillMaxLevelHash');
            $self->resetEntryBoxes($entry, $entry2);
        });
        $button5->set_tooltip_text('Clear the list of patterns');
        $grid->attach($button5, 10, 9, 2, 1);

        # 'Dump' button can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub skills2Tab {

        # Skills2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->skills2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Skills advance list', 'Skills advance cycle'],
        );

        # Skills advance list
        $self->addLabel($grid, '<b>Skills advance list</b>',
            0, 6, 0, 2);
        $self->addLabel($grid, '<i>Pre-determined list of skills to advance</i>',
            1, 6, 2, 4);
        $self->addTextView($grid, 'advanceOrderList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Skills advance cycle
        $self->addLabel($grid, '<b>Skills advance cycle</b>',
            0, 12, 6, 8);
        $self->addLabel($grid, '<i>Pre-determined cycle of skills to advance</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'advanceCycleList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Default advance method
        $self->addLabel($grid, '<b>Default advance method</b>',
            6, 12, 0, 2);
        @comboList = ('order', 'cycle', 'combo');
        $self->addComboBox($grid, 'advanceMethod', \@comboList, '',
            TRUE,               # No 'undef' value used
            7, 12, 2, 4);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Profile::Race;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Profile Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Profile::Race')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of profile is being
        #   edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # If this is a current profile, set a flag
        if ($self->session->currentRace && $self->session->currentRace eq $self->editObj) {

            $self->ivPoke('currentFlag', TRUE);
        }

        $self->initialTab();        # Inherited from GA::EditWin::Generic::Profile
        $self->notesTab();          # Inherited from GA::EditWin::Generic::Profile
        $self->commandsTab();       # Inherited from GA::EditWin::Generic::Profile
        $self->fightTab();          # Inherited from GA::EditWin::Generic::Profile
        $self->interactionTab();    # Inherited from GA::EditWin::Generic::Profile
        $self->privateDataTab(      # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private data hash',
            'Data created by your own plugins and scripts, but stored in this profile',
        );

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs (all inherited from other objects)

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Profile::Char;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Profile Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Profile::Char')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which category of profile is being
        #   edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # If this is a current profile, set a flag
        if ($self->session->currentChar && $self->session->currentChar eq $self->editObj) {

            $self->ivPoke('currentFlag', TRUE);
        }

        $self->settingsTab();
        $self->initialTab();        # Inherited from GA::EditWin::Generic::Profile
        $self->notesTab();          # Inherited from GA::EditWin::Generic::Profile
        $self->questsTab();
        $self->skillsTab();
        $self->attacksTab();
        $self->affectsTab();
        $self->statsTab();
        $self->statusTab();
        $self->inventoryTab();
        $self->objectsTab();        # Inherited from GA::Generic::EditWin
        $self->privateDataTab(      # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private',
            'Private data hash',
            'Data created by your own plugins and scripts, but stored in this profile',
        );

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves the changes made to the profile. If it was a temporary profile, create a real
        #   profile, and save the changes to the latter
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $newGuild, $newRace,
            @cmdList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # For a temporary profile...
            if ($self->tempFlag) {

                my ($profName, $result, $profObj, $number);

                $profName = $self->ivShow('editHash', 'name');

                # The user made changes, so we can now create a permanent profile and give it IVs
                #   specified in $self->editHash
                if ($self->editObj->category eq 'world') {

                    $result = $self->session->pseudoCmd(
                        'addworld ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } elsif ($self->editObj->category eq 'guild') {

                    $result = $self->session->pseudoCmd(
                        'addguild ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } elsif ($self->editObj->category eq 'race') {

                    $result = $self->session->pseudoCmd(
                        'addrace ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } elsif ($self->editObj->category eq 'char') {

                    $result = $self->session->pseudoCmd(
                        'addchar ' . $profName,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $result = $self->session->pseudoCmd(
                        'addcustomprofile ' . $profName . ' ' . $self->editObj->category,
                        $self->pseudoCmdMode,
                    );
                }

                if ($result) {

                    # Get the blessed reference of the new profile
                    if ($self->editObj->category eq 'world') {
                        $profObj = $axmud::CLIENT->ivShow('worldProfHash', $profName);
                    } else {
                        $profObj = $self->session->ivShow('profHash', $profName);
                    }

                    # Store the changes the user has made (ignore the 'name' IV, which has aleady
                    #   been set)
                    $self->ivDelete('editHash', 'name');

                    foreach my $key ($self->ivKeys('editHash')) {

                        $profObj->{$key} = $self->ivShow('editHash', $key);
                    }

                    # If the 'save' button was clicked, the window won't be closed. Before
                    #   re-drawing all those tabs, set the object being edited to the one we just
                    #   created
                    $self->ivPoke('editObj', $profObj);
                    $self->ivPoke('tempFlag', FALSE);
                }

            # For an existing profile...
            } else {

                # If the ->guild and/or ->race IVs have been modified, and if this is the current
                #   character profile, then the current guild/race profile has to be set (but only
                #   if this is still a current profile)
                # Compile a list of commands to use, once everything else has been done
                if (
                    $self->currentFlag
                    && $self->session->ivShow('currentProfHash', 'char') eq $self->editObj
                ) {
                    if ($self->ivExists('editHash', 'guild')) {

                        $newGuild = $self->ivShow('editHash', 'guild');
                        if (! defined $newGuild && defined $self->editObj->guild) {

                            push (@cmdList, 'unsetguild');
                            $self->ivDelete('editHash', 'guild');

                        } elsif (
                            defined $newGuild
                            && (
                                ! defined $self->editObj->guild
                                ||  $newGuild ne $self->editObj->guild
                            )
                        ) {
                            push (@cmdList, 'setguild ' . $newGuild);
                            $self->ivDelete('editHash', 'guild');
                        }
                    }

                    if ($self->ivExists('editHash', 'race')) {

                        $newRace = $self->ivShow('editHash', 'race');
                        if (! defined $newRace && defined $self->editObj->race) {

                            push (@cmdList, 'unsetrace');
                            $self->ivDelete('editHash', 'race');

                        } elsif (
                            defined $newRace
                            && (
                                ! defined $self->editObj->race
                                ||  $newRace ne $self->editObj->race
                            )
                        ) {
                            push (@cmdList, 'setrace ' . $newRace);
                            $self->ivDelete('editHash', 'race');
                        }
                    }
                }

                # Store the changes the user has made
                foreach my $key ($self->ivKeys('editHash')) {

                    $self->editObj->{$key} = $self->ivShow('editHash', $key);
                }

                # If the session's Status task is open, tell it to redraw its task window
                #   (including the background colour, in case the profile's ->lifeStatus IV has been
                #   updated)
                if ($self->session->statusTask) {

                    $self->session->statusTask->set_lifeStatusChangeFlag();
                }
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Set/rest current guild/race
            foreach my $cmd (@cmdList) {

                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);
            }

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs

    sub settingsTab {

        # Settings tab
        #
        # 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 . '->settingsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Settings');

        # Add tabs to the inner notebook
        $self->settings1Tab($innerNotebook);
        $self->settings2Tab($innerNotebook);
        $self->settings3Tab($innerNotebook);
        $self->settings4Tab($innerNotebook);
        $self->settings5Tab($innerNotebook);

        return 1;
    }

    sub settings1Tab {

        # Settings1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@guildList, @raceList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Character settings', 'Character nickname'],
        );

        # Prepare lists of guilds and races
        foreach my $profObj ($self->session->ivValues('profHash')) {

            if ($profObj->category eq 'guild') {
                push (@guildList, $profObj->name);
            } elsif ($profObj->category eq 'race') {
                push (@raceList, $profObj->name);
            }
        }

        # Left column
        $self->addLabel($grid, '<b>Character settings</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Guild',
            1, 3, 1, 2);
        $self->addComboBox($grid, 'guild', \@guildList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 1, 2);
        $self->addLabel($grid, 'Race',
            1, 3, 2, 3);
        $self->addComboBox($grid, 'race', \@raceList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);
        $self->addLabel($grid, 'Age',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'age', 'int', 0, undef,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Level',
            1, 3, 4, 5);
        $self->addEntryWithIcon($grid, 'level', 'int', 0, undef,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Bank balance',
            1, 3, 5, 6);
        $self->addEntryWithIcon($grid, 'bankBalance', 'float', 0, undef,
            3, 6, 5, 6);
        $self->addLabel($grid, 'Purse contents',
            1, 3, 6, 7);
        $self->addEntryWithIcon($grid, 'purseContents', 'float', 0, undef,
            3, 6, 6, 7);

        # Right column
        $self->addLabel($grid, 'Lives left',
            7, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'lifeCount', 'int', 0, undef,
            9, 12, 1, 2);
        $self->addLabel($grid, 'Times killed',
            7, 9, 2, 3);
        $self->addEntryWithIcon($grid, 'deathCount', 'int', 0, undef,
            9, 12, 2, 3);
        $self->addLabel($grid, 'Max lives',
            7, 9, 3, 4);
        $self->addEntryWithIcon($grid, 'lifeMax', 'int', 0, undef,
            9, 12, 3, 4);
        $self->addLabel($grid, 'Life status',
            7, 9, 4, 5);
        @comboList = ('alive', 'sleep', 'passout', 'dead');
        $self->addComboBox($grid, 'lifeStatus', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 4, 5);
        $self->addLabel($grid, 'Alignment',
            7, 9, 5, 6);
        $self->addEntryWithButton($grid, 'alignment', TRUE,
            9, 12, 5, 6);

        # Bottom section
        $self->addLabel($grid, '<b>Character nickname</b>',
            0, 12, 7, 8);
        $self->addLabel(
            $grid,
            '<i>Used in automatic logins. Can include any text, e.g. \'Gandalf the Great\'</i>',
            0, 12, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, 'nickname', 'string', 1, undef,
            0, 10, 9, 10);
        my $button = $self->addButton(
            $grid,
            'Reset',
            'Reset the character\'s nickname',
            undef,
            10, 12, 9, 10,
        );
        $button->signal_connect('clicked' => sub {

            $self->ivAdd('editHash', 'nickname', $self->editObj->name);
            $entry->set_text($self->editObj->name);
        });

        # Tab complete
        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Points - health, magic, energy, guild and social points'],
        );

        # Points - health, magic, energy, guild and social points
        $self->addLabel(
            $grid,
            '<b>Points - health, magic, energy, guild and social points</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, 'Health points',
            1, 3, 1, 2);
        $self->addEntryWithIcon($grid, 'healthPoints', 'int', 0, undef,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Maximum health points',
            1, 3, 2, 3);
        $self->addEntryWithIcon($grid, 'healthPointsMax', 'int', 0, undef,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Magic points',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'magicPoints', 'int', 0, undef,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Maximum magic points',
            1, 3, 4, 5);
        $self->addEntryWithIcon($grid, 'magicPointsMax', 'int', 0, undef,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Energy points',
            1, 3, 5, 6);
        $self->addEntryWithIcon($grid, 'energyPoints', 'int', 0, undef,
            3, 6, 5, 6);
        $self->addLabel($grid, 'Maximum energy points',
            1, 3, 6, 7);
        $self->addEntryWithIcon($grid, 'energyPointsMax', 'int', 0, undef,
            3, 6, 6, 7);

        # Right column
        $self->addLabel($grid, 'Guild points',
            7, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'guildPoints', 'int', 0, undef,
            9, 12, 1, 2);
        $self->addLabel($grid, 'Maximum guild points',
            7, 9, 2, 3);
        $self->addEntryWithIcon($grid, 'guildPointsMax', 'int', 0, undef,
            9, 12, 2, 3);
        $self->addLabel($grid, 'Social points',
            7, 9, 3, 4);
        $self->addEntryWithIcon($grid, 'socialPoints', 'int', 0, undef,
            9, 12, 3, 4);
        $self->addLabel($grid, 'Maximum social points',
            7, 9, 4, 5);
        $self->addEntryWithIcon($grid, 'socialPointsMax', 'int', 0, undef,
            9, 12, 4, 5);

        # Tab complete
        return 1;
    }

    sub settings3Tab {

        # Settings3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Points - eXperience Points, Quest Points and Other Points'],
        );

        # Points - eXperience Points, Quest Points and Other Points
        $self->addLabel(
            $grid,
            '<b>Points - eXperience Points, Quest Points and Other Points</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, 'Current XP',
            1, 3, 1, 2);
        $self->addEntryWithIcon($grid, 'xpCurrent', 'int', 0, undef,
            3, 6, 1, 2);
        $self->addLabel($grid, 'XP to next level',
            1, 3, 2, 3);
        $self->addEntryWithIcon($grid, 'xpNextLevel', 'int', 0, undef,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Total XP',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'xpTotal', 'int', 0, undef,
            3, 6, 3, 4);

        # Right column
        $self->addLabel($grid, 'Current QP',
            7, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'qpCurrent', 'int', 0, undef,
            9, 12, 1, 2);
        $self->addLabel($grid, 'QP to next level',
            7, 9, 2, 3);
        $self->addEntryWithIcon($grid, 'qpNextLevel', 'int', 0, undef,
            9, 12, 2, 3);
        $self->addLabel($grid, 'Total QP',
            7, 9, 3, 4);
        $self->addEntryWithIcon($grid, 'qpTotal', 'int', 0, undef,
            9, 12, 3, 4);

        $self->addLabel($grid, 'Current OP',
            7, 9, 4, 5);
        $self->addEntryWithIcon($grid, 'opCurrent', 'int', 0, undef,
            9, 12, 4, 5);
        $self->addLabel($grid, 'OP to next level',
            7, 9, 5, 6);
        $self->addEntryWithIcon($grid, 'opNextLevel', 'int', 0, undef,
            9, 12, 5, 6);
        $self->addLabel($grid, 'Total OP',
            7, 9, 6, 7);
        $self->addEntryWithIcon($grid, 'opTotal', 'int', 0, undef,
            9, 12, 6, 7);

        # Tab complete
        return 1;
    }

    sub settings4Tab {

        # Settings4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my $worldObj;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Wimpy settings'],
        );

        # Import the current world
        $worldObj = $self->session->currentWorld;

        # Wimpy settings
        $self->addLabel($grid, '<b>Wimpy settings</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Current world wimpy',
            1, 3, 1, 2);
        $self->addEntryWithIcon($grid, 'remoteWimpy', 'int', 0, undef,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Current ' . $axmud::SCRIPT . ' wimpy',
            1, 3, 2, 3);
        $self->addEntryWithIcon($grid, 'localWimpy', 'int', 0, undef,
            3, 6, 2, 3);


        $self->addLabel($grid, 'Maximum world wimpy',
            7, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'remoteWimpyMax', 'int', 0, undef,
            9, 12, 1, 2);


        $self->addLabel($grid, 'Maximum ' . $axmud::SCRIPT . ' wimpy',
            7, 9, 2, 3);
        $self->addEntry($grid, 'constLocalWimpyMax', FALSE,
            9, 12, 2, 3);

        # Tab complete
        return 1;
    }

    sub settings5Tab {

        # Settings5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @categoryList, @profList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Custom profile hash'],
        );

        # Custom profile hash
        $self->addLabel($grid, '<b>Custom profile hash</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of custom profiles which are current profiles for this character</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Category', 'text',
            'Profile name', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->settings5Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton(
            $grid,
            'Refresh',
            'Refresh the list of custom profiles',
            undef,
            10, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->settings5Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub settings5Tab_refreshList {

        # Resets the simple list displayed by $self->settings5Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@categoryList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->settings5Tab_refreshList',
                @_,
            );
        }

        # Get a sorted list of custom profiles (never saved to $self->editHash)
        @categoryList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('customProfHash'));

        # Compile the simple list data
        foreach my $category (@categoryList) {

            my $profObj = $self->editObj->ivShow('customProfHash', $category);

            push (@dataList, $category, $profObj->name);
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub questsTab {

        # Quests tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $worldObj,
            @columnList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->questsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Quests',
            ['Character quests', 'World quests', 'Solved quests', 'Unsolved quests'],
        );

        # Character quests
        $self->addLabel($grid, '<b>Character quests</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'No. of quests',
            1, 4, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            4, 6, 1, 2, 4, 4);
        $self->addLabel($grid, 'Quest points',
            1, 4, 2, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            4, 6, 2, 3, 8, 8);
        $self->addLabel($grid, 'Quest XP',
            1, 4, 3, 4);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            4, 6, 3, 4, 16, 16);
        $self->addLabel($grid, 'Quest cash',
            1, 4, 4, 5);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            4, 6, 4, 5, 16, 16);

        # World quests
        $worldObj = $self->session->ivShow('profHash', $self->editObj->parentWorld);

        $self->addLabel($grid, '<b>World quests</b>',
            7, 13, 0, 1);

        $self->addLabel($grid, 'No. of quests',
            8, 11, 1, 2);
        my $entry5 = $self->addEntry($grid, undef, 0,
            11, 13, 1, 2, 4, 4);

        $self->addLabel($grid, 'Quest points',
            8, 11, 2, 3);
        my $entry6 = $self->addEntry($grid, undef, 0,
            11, 13, 2, 3, 8, 8);

        $self->addLabel($grid, 'Quest XP',
            8, 11, 3, 4);
        my $entry7 = $self->addEntry($grid, undef, 0,
            11, 13, 3, 4, 16, 16);

        $self->addLabel($grid, 'Quest cash',
            8, 11, 4, 5);
        my $entry8 = $self->addEntry($grid, undef, 0,
            11, 13, 4, 5, 16, 16);

        # Solved/unsolved quests
        $self->addLabel($grid, '<b>Solved quests</b>',
            0, 6, 5, 6);
        $self->addLabel($grid, '<b>Unsolved quests</b>',
            7, 13, 5, 6);

        # Add a simple list for solved quests
        @columnList = (
            'Quest name', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 6, 6, 11);

        # Initialise the list
        $self->questsTab_resetSolvedList($slWidget, (scalar @columnList / 2));

        # Add a simple list of unsolved quests
        my $slWidget2 = $self->addSimpleList($grid, undef, \@columnList,
            8, 13, 6, 11);

        # Initialise the list
        $self->questsTab_resetUnsolvedList($slWidget2, (scalar @columnList / 2), $worldObj);

        # Add buttons
        my $button = $self->addButton($grid,
            'Marked unsolved', 'Mark the selected quest as unsolved', undef,
            1, 6, 11, 12,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my (
                $dataRef, $questName,
                @selectList,
            );

            @selectList = $slWidget->get_selected_indices();

            # If anything is selected...
            if (@selectList) {

                $dataRef = ${$slWidget->{data}}[$selectList[0]];
                ($questName) = @$dataRef;

                if ($self->editObj->ivExists('questHash', $questName)) {

                    # Mark as 'unsolved'
                    $self->session->pseudoCmd(
                        'finishquest -d <' . $questName . '>',
                        $self->pseudoCmdMode,
                    );

                    # Update both simple lists
                    $self->questsTab_resetSolvedList($slWidget, (scalar @columnList / 2));
                    $self->questsTab_resetUnsolvedList(
                        $slWidget2,
                        (scalar @columnList / 2),
                        $worldObj,
                    );

                    # Reset the contents of the entry boxes
                    $self->questsTab_resetEntryBoxes(
                        $worldObj, $entry, $entry2, $entry3, $entry4, $entry5, $entry6, $entry7,
                        $entry8,
                    );
                }
            }
        });

        my $button2 = $self->addButton($grid,
            'Marked solved', 'Mark the selected quest as solved', undef,
            8, 11, 11, 12,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            my (
                $dataRef, $questName,
                @selectList,
            );

            @selectList = $slWidget2->get_selected_indices();

            # If anything is selected...
            if (@selectList) {

                $dataRef = ${$slWidget2->{data}}[$selectList[0]];
                ($questName) = @$dataRef;
                if ($worldObj->ivExists('questHash', $questName)) {

                    # Mark as solved
                    $self->session->pseudoCmd(
                        'finishquest <' . $questName . '>',
                        $self->pseudoCmdMode,
                    );

                    # Update both simple lists
                    $self->questsTab_resetSolvedList($slWidget, (scalar @columnList / 2));
                    $self->questsTab_resetUnsolvedList(
                        $slWidget2,
                        (scalar @columnList / 2),
                        $worldObj,
                    );

                    # Reset the contents of the entry boxes
                    $self->questsTab_resetEntryBoxes(
                        $worldObj, $entry, $entry2, $entry3, $entry4, $entry5, $entry6, $entry7,
                        $entry8,
                    );
                }
            }
        });

        my $button3 = $self->addButton($grid,
            'Refresh data', 'Refresh the data displayed', undef,
            11, 13, 11, 12);
        $button3->signal_connect('clicked' => sub {

            # Update both simple lists
            $self->questsTab_resetSolvedList($slWidget, (scalar @columnList / 2));
            $self->questsTab_resetUnsolvedList(
                $slWidget2,
                (scalar @columnList / 2),
                $worldObj,
            );

            # Reset the contents of the entry boxes
            $self->questsTab_resetEntryBoxes(
                $worldObj, $entry, $entry2, $entry3, $entry4, $entry5, $entry6, $entry7,
                $entry8,
            );
        });

        # Set the initial contents of the eight entry boxes
        $self->questsTab_resetEntryBoxes(
            $worldObj, $entry, $entry2, $entry3, $entry4, $entry5, $entry6, $entry7, $entry8
        );

        # (Some) widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
            $button2->set_sensitive(FALSE);
            $button3->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub questsTab_resetEntryBoxes {

        # Resets the contents of the eight entry boxes
        #
        # Expected arguments
        #   $worldObj   - Blessed reference of the parent world profile
        #   @list       - The list of entry boxes, ordered 1-8
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $worldObj, @list) = @_;

        # Check for improper arguments
        if (! defined $worldObj || ! @list) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->questsTab_resetEntryBoxes',
                @_,
            );
        }

        $list[0]->set_text($self->editObj->questCount);
        $list[1]->set_text($self->editObj->questPointCount);
        $list[2]->set_text($self->editObj->questXPCount);
        $list[3]->set_text($self->editObj->questCashCount);
        $list[4]->set_text($worldObj->questCount);
        $list[5]->set_text($worldObj->questPointCount);
        $list[6]->set_text($worldObj->questXPCount);
        $list[7]->set_text($worldObj->questCashCount);

        return 1;
    }

    sub questsTab_resetSolvedList {

        # Resets the simple list, containing solved quests, displayed by
        #   $self->questsTab_resetSolvedList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @questList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->questsTab_resetSolvedList',
                @_,
            );
        }

        # Get a sorted list of quests
        @questList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('questHash'));

        # Reset the simple list
        $self->resetListData($slWidget, [@questList], $columns);

        return 1;
    }

    sub questsTab_resetUnsolvedList {

        # Resets the simple list, containing solved quests, displayed by
        #   $self->questsTab_resetSolvedList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #   $worldObj   - Blessed reference of the parent world profile
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $worldObj, $check) = @_;

        # Local variables
        my (
            @questList,
            %questHash, %solvedHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $worldObj || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->questsTab_resetUnsolvedList',
                @_,
            );
        }

        # Get a list of solved quests
        %solvedHash = $self->editObj->questHash;

        # Get a list of all quests from the parent world
        %questHash = $worldObj->questHash;

        # Remove from %questHash all quests that are solved
        foreach my $solved (keys %solvedHash) {

            if (exists $questHash{$solved}) {

                delete $questHash{$solved};
            }
        }

        # Sort the data
        @questList = sort {lc($a) cmp lc($b)} (keys %questHash);

        # Reset the simple list
        $self->resetListData($slWidget, [@questList], $columns);

        return 1;
    }

    sub skillsTab {

        # Skills tab
        #
        # 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 . '->skillsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'S_kills');

        # Add tabs to the inner notebook
        $self->skills1Tab($innerNotebook);
        $self->skills2Tab($innerNotebook);
        $self->skills3Tab($innerNotebook);
        $self->skills4Tab($innerNotebook);

        return 1;
    }

    sub skills1Tab {

        # Skills1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@comboList, @comboList2, @columnList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->skills1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Character skills'],
        );

        # Character skills
        $self->addLabel($grid, '<b>Character skills</b>',
            0, 10, 0, 1);
        my $button = $self->addButton($grid,
            'Dump list', 'Display this list in the \'main\' window', undef,
            10, 12, 0, 1);
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listguildskills', $self->pseudoCmdMode);
        });

        $self->addLabel($grid, 'Advance method',
            1, 3, 1, 2);
        @comboList = ('order', 'cycle', 'combo');
        my $combo = $self->addComboBox($grid, 'advanceMethod', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 1, 2);

        $self->addLabel($grid, 'Combo method status',
            7, 9, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            9, 12, 1, 2);
        if ($self->editObj->advanceMethodStatus) {

            $entry->set_text($self->editObj->advanceMethodStatus);
        }

        # Add a simple list
        @columnList = (
            'Skill', 'text',
            'Level', 'int',
            '(Max)', 'int',
            'Advances', 'int',
            'XP spent', 'int',
            'XP required', 'int',
            'Cash spent', 'int',
            'Cash required', 'int',
        );
        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->skills1Tab_refreshList($slWidget, (scalar @columnList / 2));

        # Add some buttons, which are desensitised if this isn't the current character
        my $button2 = $self->addButton($grid,
            'Advance next', 'Advance the next skill (or skills) from the advance list', undef,
            1, 3, 10, 11,
            TRUE,           # Irreversible
        );

        # Default value for the entry is 1 (skill to advance at a time)
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, 100,
            3, 6, 10, 11);
        $entry2->set_text('1');
        $self->addLabel($grid, '...skills',
            6, 8, 10, 11);

        $button2->signal_connect('clicked' => sub {

            my $number;

            if ($self->checkEntryIcon($entry2)) {

                $number = $entry2->get_text();
                $self->session->pseudoCmd('advance ' . $number, $self->pseudoCmdMode);

            } else {

                $self->session->pseudoCmd('advance', $self->pseudoCmdMode);
            }
        });

        my $button3 = $self->addButton($grid,
            'Advance many', 'Advance as many skills as possible from the advance list', undef,
            10, 12, 10, 11,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('advance -a', $self->pseudoCmdMode);
        });

        @comboList2 = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('skillLevelHash'));
        my $combo2 = $self->addComboBox($grid, undef, \@comboList2, '',
            FALSE,              # 'undef' value allowed
            1, 4, 11, 12);

        my $button4 = $self->addButton($grid,
            'Advance this skill', 'Advance the selected skill once', undef,
            4, 6, 11, 12,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my $skill = $combo2->get_active_text();

            if ($skill) {

                $self->session->pseudoCmd('advance -s ' . $skill, $self->pseudoCmdMode);
            }
        });

        my $button5 = $self->addButton(
            $grid,
            'Reset guild skills',
            'Reset character\'s skills to those defined by the guild profile',
            undef,
            10, 12, 11, 12,
            TRUE,           # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('resetguildskills', $self->pseudoCmdMode);

            $self->skills1Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Buttons only available when the current character profile is being edited
        if (! $self->session->currentChar || $self->editObj ne $self->session->currentChar) {

            $self->desensitiseWidgets($button, $button2, $button3, $button4, $button5);
        }

        # (Most) widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
            $button2->set_sensitive(FALSE);
            $entry2->set_sensitive(FALSE);
            $button3->set_sensitive(FALSE);
            $button4->set_sensitive(FALSE);
            $combo2->set_sensitive(FALSE);
            $button5->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub skills1Tab_refreshList {

        # Resets the simple list displayed by $self->skills1Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@skillList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->skills1Tab_refreshList', @_);
        }

        # Get a sorted list of skills
        @skillList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('skillLevelHash'));

        # Compile the simple list data
        foreach my $skill (@skillList) {

            my $max;

            if ($self->session->currentGuild) {

                $max = $self->session->currentGuild->ivShow('skillMaxLevelHash', $skill);
            }

            push (@dataList,
                $skill,
                $self->editObj->ivShow('skillLevelHash', $skill),
                $max,
                $self->editObj->ivShow('skillAdvanceCountHash', $skill),
                $self->editObj->ivShow('skillTotalXPHash', $skill),
                $self->editObj->ivShow('skillNextXPHash', $skill),
                $self->editObj->ivShow('skillTotalCashHash', $skill),
                $self->editObj->ivShow('skillNextCashHash', $skill),
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub skills2Tab {

        # Skills2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @objList, @dataList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->skills2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Character skill history'],
        );

        # Character skill history
        $self->addLabel($grid, '<b>Character skill history</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Historical list of character\'s skill advancements',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Skill', 'text',
            'Method', 'text',
            'Level', 'int',
            'Advances', 'int',
            'XP spent', 'int',
            'XP required', 'int',
            'Cash spent', 'int',
            'Cash required', 'int',
        );
        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list - each skill advancement is stored in a single GA::Obj::SkillHistory
        @objList = $self->editObj->skillHistoryList;

        foreach my $historyObj (@objList) {

            push (@dataList,
                $historyObj->skill,
                $historyObj->advanceMethod,
                $historyObj->skillLevel,
                $historyObj->skillAdvanceCount,
                $historyObj->skillThisXP,
                $historyObj->skillNextXP,
                $historyObj->skillThisCash,
                $historyObj->skillNextCash,
            );
        }

        # Initialise the list
        $self->resetListData($slWidget, [@dataList], (scalar @columnList / 2));

        my $button = $self->addButton($grid,
            'Dump list', 'Display this list in the \'main\' window', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listadvancehistory', $self->pseudoCmdMode);
        });


        # Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub skills3Tab {

        # Skills3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->skills3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Advance skills - ordered list'],
        );

        # Advance skills - ordered list
        $self->addLabel($grid, '<b>Advance skills - ordered list</b>',
            0, 10, 0, 2);
        my $button = $self->addButton($grid,
            'Dump lists', 'Display these lists in the \'main\' window', undef,
            10, 12, 0, 2);
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listadvance', $self->pseudoCmdMode);
        });

        $self->addLabel($grid,
            '<i>Pre-determined list of skills to advance (empty if not used)</i>',
            1, 10, 2, 4);
        my $textView = $self->addTextView($grid, 'advanceOrderList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        my $buffer = $textView->get_buffer();

        $self->addLabel($grid,
            '<i>Current list of skills to advance (emptied as they are used up)</i>',
            1, 10, 8, 10);
        my $button2 = $self->addButton($grid,
            'Skip next', 'Skip the next skill in the list below', undef,
            10, 12, 8, 10,
            TRUE,           # Irreversible
        );

        my $textView2 = $self->addTextView($grid, 'currentAdvanceOrderList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        my $buffer2 = $textView2->get_buffer();
        $button2->signal_connect('clicked' => sub {

            # Skip the next skill
            $self->session->pseudoCmd('skipadvance -o', $self->pseudoCmdMode);

            # Update the two textviews
            $buffer->set_text(join("\n", $self->editObj->ivPeek('advanceOrderList')));
            $buffer2->set_text(join("\n", $self->editObj->ivPeek('currentAdvanceOrderList')));

        });

        # Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
            $button2->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub skills4Tab {

        # Skills4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->skills4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Advance skills - cycle list'],
        );

        # Advance skills - cycle list
        $self->addLabel($grid, '<b>Advance skills - cycle list</b>',
            0, 10, 0, 2);
        my $button = $self->addButton($grid,
            'Dump lists', 'Display these lists in the \'main\' window', undef,
            10, 12, 0, 2);
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listadvance', $self->pseudoCmdMode);
        });

        $self->addLabel($grid,
            '<i>Pre-determined list of skills to advance (empty if not used)</i>',
            1, 12, 2, 4);
        my $textView = $self->addTextView($grid, 'advanceCycleList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        my $buffer = $textView->get_buffer();

        $self->addLabel($grid,
            '<i>Current cycle of skills to advance (list refreshed when they are used up)</i>',
            1, 10, 8, 10);
        my $button2 = $self->addButton($grid,
            'Skip next', 'Skip the next skill in the list below', undef,
            10, 12, 8, 10,
            TRUE,           # Irreversible
        );

        my $textView2 = $self->addTextView($grid, 'currentAdvanceCycleList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );
        my $buffer2 = $textView2->get_buffer();
        $button2->signal_connect('clicked' => sub {

            # Skip the next skill
            $self->session->pseudoCmd('skipadvance -c', $self->pseudoCmdMode);

            # Update the two textviews
            $buffer->set_text(join("\n", $self->editObj->ivPeek('advanceCycleList')));
            $buffer2->set_text(join("\n", $self->editObj->ivPeek('currentAdvanceCycleList')));

        });

        # Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
            $button2->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub attacksTab {

        # Attacks tab
        #
        # 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 . '->attacksTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Attacks');

        # Add tabs to the inner notebook
        $self->attacks1Tab($innerNotebook);
        $self->attacks2Tab($innerNotebook);
        $self->attacks3Tab($innerNotebook);
        $self->attacks4Tab($innerNotebook);
        $self->attacks5Tab($innerNotebook);

        return 1;
    }

    sub attacks1Tab {

        # Attacks1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attacks1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Fight statistics', 'Interaction statistics', 'Coward statistics'],
        );

        # Fight statistics
        $self->addLabel($grid, '<b>Fight statistics</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'No. fights',
            1, 3, 1, 2);
        $self->addEntryWithIcon($grid, 'fightCount', 'int', 0, undef,
            3, 6, 1, 2);
        $self->addLabel($grid, 'No. kills',
            1, 3, 2, 3);
        $self->addEntryWithIcon($grid, 'killCount', 'int', 0, undef,
            3, 6, 2, 3);
        $self->addLabel($grid, 'No. wimpys',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'wimpyCount', 'int', 0, undef,
            3, 6, 3, 4);
        $self->addLabel($grid, 'No. fight defeats',
            1, 3, 4, 5);
        $self->addEntryWithIcon($grid, 'fightDefeatCount', 'int', 0, undef,
            3, 6, 4, 5);

        # Coward statistics
        $self->addLabel($grid, '<b>Coward statistics</b>',
            0, 6, 6, 7);
        $self->addLabel($grid, 'No. targets fled',
            1, 3, 7, 8);
        $self->addEntryWithIcon($grid, 'fleeCount', 'int', 0, undef,
            3, 6, 7, 8);
        $self->addLabel($grid, 'No. targets escaped',
            1, 3, 8, 9);
        $self->addEntryWithIcon($grid, 'escapeCount', 'int', 0, undef,
            3, 6, 8, 9);

        # Interaction statistics
        $self->addLabel($grid, '<b>Interaction statistics</b>',
            6, 12, 0, 1);
        $self->addLabel($grid, 'No. interactions',
            7, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'interactCount', 'int', 0, undef,
            9, 12, 1, 2);
        $self->addLabel($grid, 'No. successes',
            7, 9, 2, 3);
        $self->addEntryWithIcon($grid, 'interactSuccessCount', 'int', 0, undef,
            9, 12, 2, 3);
        $self->addLabel($grid, 'No. fails',
            7, 9, 3, 4);
        $self->addEntryWithIcon($grid, 'interactFailCount', 'int', 0, undef,
            9, 12, 3, 4);
        $self->addLabel($grid, 'No. fight conversions',
            7, 9, 4, 5);
        $self->addEntryWithIcon($grid, 'interactFightCount', 'int', 0, undef,
            9, 12, 4, 5);
        $self->addLabel($grid, 'No. disasters',
            7, 9, 5, 6);
        $self->addEntryWithIcon($grid, 'interactDisasterCount', 'int', 0, undef,
            9, 12, 5, 6);

        # Tab complete
        return 1;
    }

    sub attacks2Tab {

        # Attacks2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attacks2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Fight history (1/2)'],
        );

        # Fight history (1/2)
        $self->addLabel($grid, '<b>Fight history (1/2)</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Record of things killed (sorted by main noun - not updated by the Attack task)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Main noun', 'text',
            'Number of kills', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'fightVictimHash');

        # Add entries/comboboxes for adding new records
        $self->addLabel($grid, 'Main noun',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Number of kills',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'fightVictimHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($noun, $count);

            $noun = $entry->get_text();
            $count = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('fightVictimHash', $noun, $count);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'fightVictimHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub attacks3Tab {

        # Attacks3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attacks3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Fight history (2/2)'],
        );

        # Fight history (2/2)
        $self->addLabel($grid, '<b>Fight history (2/2)</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Record of things killed (sorted by base string)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Base string', 'text',
            'Number of kills', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'fightVictimStringHash');

        # Add entries/comboboxes for adding new records
        $self->addLabel($grid, 'Base string',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Number of kills',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'fightVictimStringHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($baseString, $count);

            $baseString = $entry->get_text();
            $count = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('fightVictimStringHash', lc($baseString), $count);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'fightVictimStringHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub attacks4Tab {

        # Attacks4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attacks4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Interaction history (1/2)'],
        );

        # Interaction history (1/2)
        $self->addLabel($grid, '<b>Interaction history (1/2)</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Record of successful interactions (sorted by main noun - not updated by the Attack'
            . ' task)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Main noun', 'text',
            'Success count', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'interactionVictimHash');

        # Add entries/comboboxes for adding new records
        $self->addLabel($grid, 'Main noun',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Success count',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'interactionVictimHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($noun, $count);

            $noun = $entry->get_text();
            $count = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('interactionVictimHash', $noun, $count);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionVictimHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub attacks5Tab {

        # Attacks5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->attacks5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Interaction history (2/2)'],
        );

        # Interaction history (2/2)
        $self->addLabel($grid, '<b>Interaction history (2/2)</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Record of successful interactions (sorted by base string)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Base string', 'text',
            'Success count', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV(
            $slWidget,
            scalar (@columnList / 2),
            'interactionVictimStringHash',
        );

        # Add entries/comboboxes for adding new records
        $self->addLabel($grid, 'Base string',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Success count',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'interactionVictimStringHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($baseString, $count);

            $baseString = $entry->get_text();
            $count = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV(
                    'interactionVictimStringHash',
                    lc($baseString),
                    $count,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionVictimStringHash',
                );
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub affectsTab {

        # Affects tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->affectsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'A_ffects',
            ['Character affects'],
        );

        # Character affects
        $self->addLabel($grid, '<b>Character affects</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of spells (etc) that currently affect this character</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Affect', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'affectHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Affect',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Value (not in use)',
            7, 10, 8, 9);
        # (Left commented out, in case we want to assign values to the IV's key-value pairs later)
#        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', 0, undef,
#           10, 12, 8, 9);
        $self->addEntry($grid, undef, FALSE,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'affectHash',
            9,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my $affect = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('affectHash', $affect, undef);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'affectHash');
                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub statsTab {

        # Stats tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->statsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'S_tats',
            ['Character stats'],
        );

        # Character stats
        $self->addLabel($grid, '<b>Character stats</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of stats for this character and their current values</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Stat', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Stat',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($grid, 'Current value',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'statHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($unit, $value);

            $unit = $entry->get_text();
            $value = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('statHash', $unit, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub statusTab {

        # Status tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->statusTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'Stat_us',
            ['Status commands'],
        );

        # Status commands
        $self->addLabel($grid, '<b>Status commands</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of commands sent to the world to update the Status task\'s variables</i>',
            1, 12, 1, 2);
        $self->addCheckButton(
            $grid,
            'Send commands as soon as the Status task is initialised',
            'statusCmdFlag',
            TRUE,
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Command', 'text',
            'How often', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statusCmdHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Command',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($grid, 'Sent how often (in seconds):',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'statusCmdHash',
            11,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($cmd, $time);

            $cmd = $entry->get_text();
            $time = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('statusCmdHash', $cmd, $time);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'statusCmdHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub inventoryTab {

        # Inventory tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->inventoryTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'In_ventory',
            ['Inventory commands'],
        );

        # Inventory commands
        $self->addLabel($grid, '<b>Inventory commands</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of commands sent to the world to update the Inventory task\'s variables</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Command', 'text',
            'Sent how often', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'inventoryCmdHash');

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($grid, 'Command',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($grid, 'Sent how often (in seconds):',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'inventoryCmdHash',
            11,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($cmd, $time);

            $cmd = $entry->get_text();
            $time = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('inventoryCmdHash', $cmd, $time);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2), 'inventoryCmdHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub objectsTab {

        # Objects tab - called by $self->setupNotebook
        #
        # 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 . '->objectsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'O_bjects');

        # Add tabs to the inner notebook
        $self->objects1Tab($innerNotebook); # Inherited from GA::Generic::EditWin
        $self->objects2Tab($innerNotebook); # Inherited from GA::Generic::EditWin

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Profile::Template;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Profile::Template')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook, once the user has specified the profile template's category
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Category',
            ['Profile template category'],
        );

        # Profile template category
        $self->addLabel($grid, '<b>Profile template category</b>',
            0, 6, 0, 1);

        # Explanatory label - contents to be set to 'Choose a category and press enter', in a moment
        my $label = $self->addLabel($grid, '',
            1, 6, 2, 3);

        # Entry box for the template category
        my $entry;

        if ($self->tempFlag) {

            # $self->editObj is a temporary template, so the entry box must be sensitive
            $entry = $self->addEntry($grid, undef, TRUE,
                1, 6, 1, 2, 16, 16);

            $entry->signal_connect (activate => sub {

                my ($text, $matchFlag);

                $text = $entry->get_text();

                # Check that the category is not one of the standard categories ('world', 'guild',
                #   'race' and 'char') and that a profile template named $text doesn't already exist
                OUTER: foreach my $category ($axmud::CLIENT->constProfPriorityList) {

                    if ($category eq $text) {

                        $matchFlag = TRUE;
                        last OUTER;
                    }
                }

                if ($matchFlag || $self->session->ivExists('templateHash', $text)) {

                    $label->set_markup(
                        '<i>The profile category \'' . $text . '\' already exists</i>',
                    );

                    $self->winShowAll($self->_objClass . '->setupNotebook');

                } elsif (! $axmud::CLIENT->nameCheck($text, 16)) {

                    $label->set_markup('<i>Max 16 chars: A-Z a-z _ 0-9</i>');
                    $self->winShowAll($self->_objClass . '->setupNotebook');

                } else {

                    # Replace the explanatory label
                    $label->set_markup('<i>You may now edit the template</i>');
                    # Desensitise the entry box - once the template category is entered, it's final
                    $entry->set_sensitive(FALSE);
                    # Make sure the OK/reset/save buttons are now sensitised
                    $self->okButton->set_sensitive(TRUE);
                    $self->resetButton->set_sensitive(TRUE);
                    $self->saveButton->set_sensitive(TRUE);

                    # Store the category until all IVs are copied to the profile template
                    $self->ivAdd('editHash', 'category', $text);

                    # Set up the remaining tabs
                    $self->expandNotebook();

                    # Render the changes
                    $self->winShowAll($self->_objClass . '->setupNotebook');
                }
            });

            $entry->signal_connect('changed' => sub {

                my ($value, $matchFlag, $result);

                $value = $entry->get_text();

                # Check whether $text is a valid value, or not
                OUTER: foreach my $category ($axmud::CLIENT->constProfPriorityList) {

                    if ($category eq $value) {

                        $matchFlag = TRUE;
                        last OUTER;
                    }
                }

                if (
                    $matchFlag
                    || $self->session->ivExists('templateHash', $value)
                    || $axmud::CLIENT->nameCheck($value, 16)
                ) {
                    $entry->set_icon_from_stock('secondary', 'gtk-yes');
                } else {
                    $entry->set_icon_from_stock('secondary', 'gtk-no');
                }
            });

            $label->set_markup('<i>Choose a category and press \'enter\'</i>');
            # (Entry is initially empty)
            $entry->set_icon_from_stock('secondary', 'gtk-no');

        } else {

            # $self->editObj is an existing profile template, so the entry box must be insensitive
            $entry = $self->addEntry($grid, 'category', FALSE,
                1, 6, 1, 2, 16, 16);

            $label->set_markup('<i>(The template category can\'t be changed)</i>');

            # Set up the remaining tabs
            $self->expandNotebook();
        }

        # Set up the rest of the tab
        $self->categoryTab($grid);

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        # Expand the notebook
        $self->propertiesTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves the changes made to the profile template. If it was a temporary template, create a
        #   real template, and save the changes to the latter
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my %editHash;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # For a temporary profile template...
            if ($self->tempFlag) {

                my ($category, $result, $templObj, $number);

                $category = $self->ivShow('editHash', 'category');

                # The user made changes, so we can now create a permanent profile template and give
                #   it IVs specified in $self->editHash
                $result = $self->session->pseudoCmd(
                    'addtemplate ' . $category,
                    $self->pseudoCmdMode,
                );

                if ($result) {

                    # Get the blessed reference of the new profile template
                    $templObj = $self->session->ivShow('templateHash', $category);

                    # Store the changes the user has made (ignore the 'category' IV, which has
                    #   aleady been set)
                    $self->ivDelete('editHash', 'category');

                    foreach my $key ($self->ivKeys('editHash')) {

                        $templObj->{$key} = $self->ivShow('editHash', $key);
                    }

                    # If the 'save' button was clicked, the window won't be closed. Before
                    #   re-drawing all those tabs, set the object being edited to the one we just
                    #   created
                    $self->ivPoke('editObj', $templObj);
                    $self->ivPoke('tempFlag', FALSE);
                }

            # For an existing profile template...
            } else {

                # Import ->editHash which contains not just the changes the user has made, but any
                #   $self->editObj's properties that haven't been changed
                %editHash = $self->editHash;

                # Check each of $self->editObj's existing properties against %editHash
                foreach my $property ($self->editObj->ivList()) {

                    # (Ignore standard IVs...)
                    if ($property ne 'category' && substr($property, 0, 1) ne '_') {

                        if (exists $editHash{$property}) {

                            # The user didn't delete the property. Update the property's value,
                            #   which will be a scalar, or a list/hash reference
                            $self->editObj->{$property} = $editHash{$property};
                            delete $editHash{$property};

                        } else {

                            # The user deleted the property
                            delete $self->editObj->{$property};
                        }
                    }
                }

                # If the user has added any new properties, add them to $self->editObj
                foreach my $property (keys %editHash) {

                    $self->editObj->{$property} = $editHash{$property};
                }
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs

    sub categoryTab {

        # Category tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->categoryTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Fixed? (No more changes permitted)
        $self->addLabel($grid, '<b>Fixed? (No more changes permitted)</b>',
            7, 13, 0, 1);

        my $checkButton = Gtk3::CheckButton->new();
        if ($self->editObj->constFixedFlag) {

            $checkButton->set_active(TRUE);
        }
        $checkButton->set_sensitive(FALSE);
        $grid->attach($checkButton, 8, 1, 5, 1);

        # Tab complete
        return 1;
    }

    sub propertiesTab {

        # Properties tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->propertiesTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'P_roperties',
            ['Profile template properties'],
        );

        # Profile template properties
        $self->addLabel($grid, '<b>Profile template properties</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>Hash of properties that will be given to all profiles based on this template</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Property name', 'text',
            'Type', 'text',
            'Value(s)', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Copy all existing properties of $self->editObj into $self->editHash...
        $self->propertiesTab_setup();
        # ...then initialise the simple list
        $self->propertiesTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add an entry for adding new data to the private hash
        $self->addLabel($grid, 'Property (IV) name',
            1, 3, 8, 9);
        # Show an icon for when something is entered; however the icon isn't checked (as would
        #   normally happen)
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 8, 9);

        # The property's corresponding value is either a scalar, or a reference to a list or hash
        # Add three buttons to allow the user to add one of these three values
        my $button = $self->addButton($grid, 'Add scalar...', 'Add a scalar value', undef,
            6, 8, 8, 9);
        $button->signal_connect('clicked' => sub {

            my $property = $entry->get_text();

            if ($property) {

                # Check that the key isn't a standard property name, or an 'unwriteable' property
                #   which begins with and underline (neither of which can be added as a new
                #   property)
                if ($axmud::CLIENT->ivExists('constProfStandardHash', $property)) {

                    $self->showMsgDialogue(
                        'Add scalar property',
                        'error',
                        'The property \'' . $property . '\' is a reserved name which cannot be'
                        . ' added',
                        'ok',
                    );

                } elsif (substr($property, 0, 1) eq '_') {

                    $self->showMsgDialogue(
                        'Add scalar property',
                        'error',
                        'Properties beginning with an underline character cannot be added',
                        'ok',
                    );

                # Check the key hasn't already been created
                } elsif (! $self->ivExists('editHash', $property)) {

                    # Create the property, set to the value to 'undef'
                    $self->ivAdd('editHash', $property, undef);

                    # Prompt the user for a scalar value
                    $self->promptScalar(
                        $property,
                        undef,          # Mode 1
                        undef,
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );

                    # Reset the entry box
                    $entry->set_text('');
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Add list...', 'Add a list value', undef,
            8, 10, 8, 9);
        $button2->signal_connect('clicked' => sub {

            my $property = $entry->get_text();

            if ($property) {

                # Check the key hasn't already been created
                if (! $self->ivExists('editHash', $property)) {

                    # Create the property, set to the value to an anonymous list
                    $self->ivAdd('editHash', $property, []);

                    # Prompt the user for a list value
                    $self->promptList(
                        $property,
                        undef,          # Mode 1
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );

                    # Reset the entry box
                    $entry->set_text('');
                }
            }
        });

        my $button3 = $self->addButton($grid, 'Add hash...', 'Add a hash value', undef,
            10, 12, 8, 9);
        $button3->signal_connect('clicked' => sub {

            my $property = $entry->get_text();

            if ($property) {

                # Check the key hasn't already been created
                if (! $self->ivExists('editHash', $property)) {

                    # Create the property, set to the value to an anonymous hash
                    $self->ivAdd('editHash', $property, {});

                    # Prompt the user for a hash value
                    $self->promptHash(
                        $property,
                        undef,          # Mode 1
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );

                    # Reset the entry box
                    $entry->set_text('');
                }
            }
        });

        # Add the usual editing buttons
        my $button4 = Gtk3::Button->new('Edit');
        $button4->signal_connect('clicked' => sub {

            my ($property, $type);

            ($property) = $self->getSimpleListData($slWidget, 0);
            if (defined $property) {

                $type = ref $self->ivShow('editHash', $property);

                # Call ->promptScalar, ->promptList and ->promptHash
                if ($type eq 'ARRAY') {

                    $self->promptList(
                        $property,
                        undef,          # Mode 1
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );

                } elsif ($type eq 'HASH') {

                    $self->promptHash(
                        $property,
                        undef,          # Mode 1
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );

                } else {

                    $self->promptScalar(
                        $property,
                        undef,          # Mode 1
                        undef,
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );
                }
            }
        });
        $button4->set_tooltip_text('Edit the selected propery');
        $grid->attach($button4, 1, 9, 3, 1);

        my $button5 = Gtk3::Button->new('Delete');
        $button5->signal_connect('clicked' => sub {

            my ($property) = $self->getSimpleListData($slWidget, 0);
            if (defined $property) {

                $self->ivDelete('editHash', $property);

                # Update the simple list
                $self->propertiesTab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });
        $button5->set_tooltip_text('Delete the selected property');
        $grid->attach($button5, 6, 9, 2, 1);

        my $button6 = Gtk3::Button->new('Reset');
        $button6->signal_connect('clicked' => sub {

            # Copy all existing properties of $self->editObj into $self->editHash, replacing its
            #   previous contents...
            $self->propertiesTab_setup();
            # ...then update the simple list
            $self->propertiesTab_refreshList($slWidget, scalar (@columnList / 2));
        });
        $button6->set_tooltip_text('Reset the list of properties');
        $grid->attach($button6, 8, 9, 2, 1);

        my $button7 = Gtk3::Button->new('Clear');
        $button7->signal_connect('clicked' => sub {

            $self->ivEmpty('editHash');

            # Update the simple list
            $self->propertiesTab_refreshList($slWidget, scalar (@columnList / 2));
        });
        $button7->set_tooltip_text('Clear the list of properties');
        $grid->attach($button7, 10, 9, 2, 1);

        # Tab complete
        return 1;
    }

    sub propertiesTab_setup {

        # Called by $self->propertiesTab
        # Copies all of the available properties into $self->editHash, replacing the existing
        #   contents of $self->editHash (if any)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $category,
            @useList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->propertiesTab_setup', @_);
        }

        # Import the list of the template's existing IVs and remove the standard IVs which can't
        #   be edited
        foreach my $item ($self->editObj->ivList()) {

            if ($item ne 'category' && substr($item, 0, 1) ne '_') {

                push (@useList, $item);
            }
        }

        # Copy all existing properties into $self->editHash, but preserve the existing value for
        #   'category' (present when this is a temporary template profile, and needed to create the
        #   permanent object)
        if ($self->ivExists('editHash', 'category')) {

            $category = $self->ivShow('editHash', 'category');
        }

        $self->ivEmpty('editHash');
        if ($category) {

            $self->ivAdd('editHash', 'category', $category);
        }

        foreach my $property (@useList) {

            $self->ivAdd('editHash', $property, $self->editObj->{$property});
        }

        return 1;
    }

    sub propertiesTab_refreshList {

        # Called by $self->propertiesTab
        # Populates the GA::Obj::SimpleList displayed in this window with all the properties (IVs)
        #   stored in the template, except the standard ones which can't be edited in this tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->propertiesTab_refreshList',
                @_,
            );
        }

        # Compile the simple list data
        foreach my $key (sort {lc($a) cmp lc($b)} ($self->ivKeys('editHash'))) {

            my (
                $value, $typeString, $valueString, $type,
                %hash,
            );

            $value = $self->ivShow('editHash', $key);

            if (! defined $value) {

                $typeString = 'scalar';
                $valueString = '<undef>';

            } else {

                $type = ref $value;
                if ($type eq 'ARRAY') {

                    $typeString = 'list';
                    foreach my $item (@$value) {

                        if (! defined $valueString) {
                            $valueString = $item;
                        } else {
                            $valueString .= ', ' . $item;
                        }
                    }

                } elsif ($type eq 'HASH') {

                    $typeString = 'hash';
                    foreach my $thisKey (sort {lc($a) cmp lc($b)} (keys %$value)) {

                        my $thisValue = $$value{$thisKey};
                        if (! defined $thisValue) {

                            $thisValue = '<undef>';
                        }

                        if (! defined $valueString) {
                            $valueString = $thisKey . ':' . $thisValue;
                        } else {
                            $valueString .= ', ' . $thisKey . ':' . $thisValue;
                        }
                    }

                } else {

                    $typeString = 'scalar';
                    $valueString = $value;
                }
            }

            push (@dataList,
                $key,
                $typeString,
                $valueString,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Profile::Custom;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::EditWin::Generic::Profile Games::Axmud::Generic::EditWin
        Games::Axmud::Generic::ConfigWin Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win
        Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Profile::Custom')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

#   sub setupNotebook {}        # Inherited from GA::Generic::ConfigWin

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        # If this is a current profile, set a flag
        if (
            $self->session->ivExists('currentProfHash', $self->editObj->category)
            && $self->session->ivShow('currentProfHash', $self->editObj->category) eq $self->editObj
        ) {
            $self->ivPoke('currentFlag', TRUE);
        }

        # v0.5.007 commented out, because custom profiles don't have the necessary properties
#        $self->initialTab();        # Inherited from GA::EditWin::Generic::Profile
#        $self->notesTab();          # Inherited from GA::EditWin::Generic::Profile
#        $self->commandsTab();       # Inherited from GA::EditWin::Generic::Profile
#        $self->privateDataTab(      # Inherited from GA::Generic::EditWin
#            'privateHash',
#            '_Private',
#            'Private data hash',
#            'Data created by your own plugins and scripts, but stored in this profile',
#        );
        $self->propertiesTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub propertiesTab {

        # Properties tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->propertiesTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Properties',
            ['Custom properties'],
        );

        # Custom properties
        $self->addLabel($grid, '<b>Custom properties</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Hash of properties inherited from the profile template</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Property name', 'text',
            'Type', 'text',
            'Value(s)', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Copy all existing properties of $self->editObj into $self->editHash...
        $self->propertiesTab_setup();
        # ...then initialise the simple list
        $self->propertiesTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons, numbered to correspond with those used in
        #   GA::EditWin::Profile::Template->propertiesTab
        my $button4 = Gtk3::Button->new('Edit');
        $button4->signal_connect('clicked' => sub {

            my ($property, $type);

            ($property) = $self->getSimpleListData($slWidget, 0);
            if (defined $property) {

                $type = ref $self->editObj->{$property};

                # Call ->promptScalar, ->promptList and ->promptHash
                if ($type eq 'ARRAY') {

                    $self->promptList(
                        $property,
                        undef,          # Mode 1
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );

                } elsif ($type eq 'HASH') {

                    $self->promptHash(
                        $property,
                        undef,          # Mode 1
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );

                } else {

                    $self->promptScalar(
                        $property,
                        undef,          # Mode 1
                        undef,
                        undef,
                        FALSE,
                        'propertiesTab_refreshList',
                        $slWidget,
                        scalar (@columnList / 2),
                    );
                }
            }
        });
        $button4->set_tooltip_text('Edit the selected propery');
        $grid->attach($button4, 1, 10, 2, 1);

        my $button6 = Gtk3::Button->new('Reset');
        $button6->signal_connect('clicked' => sub {

            # Copy all existing properties of $self->editObj into $self->editHash, replacing its
            #   previous contents...
            $self->propertiesTab_setup();
            # ...then update the simple list
            $self->propertiesTab_refreshList($slWidget, scalar (@columnList / 2));
        });
        $button6->set_tooltip_text('Reset the list of properties');
        $grid->attach($button6, 10, 10, 2, 1);

        # Tab complete
        return 1;
    }

    sub propertiesTab_setup {

        # Called by $self->propertiesTab
        # Copies all of the available properties into $self->editHash, replacing the existing
        #   contents of $self->editHash (if any)
        #
        # 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 . '->propertiesTab_setup', @_);
        }

        # Get a list of properties that were created from the template profile (i.e. all IVs that
        #   aren't in GA::Client->constProfStandardHash)
        foreach my $iv ($self->editObj->ivList()) {

            if (! $axmud::CLIENT->ivExists('constProfStandardHash', $iv)) {

                $self->ivAdd('editHash', $iv, $self->editObj->{$iv});
            }
        }

        return 1;
    }

    sub propertiesTab_refreshList {

        # Called by $self->propertiesTab
        # Populates the GA::Obj::SimpleList displayed in this window with all the properties (IVs)
        #   stored in the template, except the standard ones which can't be edited in this tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->propertiesTab_refreshList',
                @_,
            );
        }

        # Compile the simple list data
        foreach my $key (sort {lc($a) cmp lc($b)} ($self->ivKeys('editHash'))) {

            my (
                $value, $typeString, $valueString, $type,
                %hash,
            );

            $value = $self->ivShow('editHash', $key);

            if (! defined $value) {

                $typeString = 'scalar';
                $valueString = '<undef>';

            } else {

                $type = ref $value;
                if ($type eq 'ARRAY') {

                    $typeString = 'list';
                    foreach my $item (@$value) {

                        if (! defined $valueString) {
                            $valueString = $item;
                        } else {
                            $valueString .= ', ' . $item;
                        }
                    }

                } elsif ($type eq 'HASH') {

                    $typeString = 'hash';
                    foreach my $thisKey (sort {lc($a) cmp lc($b)} (keys %$value)) {

                        my $thisValue = $$value{$thisKey};
                        if (! defined $thisValue) {

                            $thisValue = '<undef>';
                        }

                        if (! defined $valueString) {
                            $valueString = $thisKey . ':' . $thisValue;
                        } else {
                            $valueString .= ', ' . $thisKey . ':' . $thisValue;
                        }
                    }

                } else {

                    $typeString = 'scalar';
                    $valueString = $value;
                }
            }

            push (@dataList,
                $key,
                $typeString,
                $valueString,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Protect;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        # (This 'edit' window is used by both protected and monitored objects)
        if (
            $self->editObj
            && ! $self->editObj->isa('Games::Axmud::Obj::Protect')
            && ! $self->editObj->isa('Games::Axmud::Obj::Monitor')
        ) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Nouns',
            ['Nouns', 'Categories'],
        );

        # Set up the rest of the tab
        $self->nounsTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->adjectivesTab();
        $self->unknownsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub nounsTab {

        # Nouns tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nounsTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Nouns
        $self->addLabel($grid, '<b>Nouns</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Main noun</i>',
            1, 3, 1, 2);
        $self->addEntryWithIcon($grid, 'noun', 'string', 1, undef,
            3, 6, 1, 2);

        $self->addLabel($grid, '<i>List of other nouns</i>',
            1, 12, 2, 3);
        $self->addTextView($grid, 'otherNounList', TRUE,
            1, 12, 3, 6);

        # Categories
        $self->addLabel($grid, '<b>Categories</b>',
            0, 12, 6, 7);

        $self->addLabel($grid,
            '<i>List of applicable categories (\'weapon\', \'portable\' etc)</i>',
            1, 12, 7, 8);
        my $textView = $self->addTextView($grid, 'categoryList', FALSE,
            1, 12, 8, 11);

        @comboList = (
            'region', 'room',
            'weapon', 'armour', 'garment',
            'char', 'minion', 'sentient', 'creature',
            'portable', 'decoration', 'custom',
        );

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            1, 4, 11, 12);

        my $button = $self->addButton($grid, 'Add', 'Add the selected category', undef,
            4, 6, 11, 12);
        $button->signal_connect('clicked' => sub {

            my (
                $choice, $buffer,
                @categoryList,
            );

            $choice = $combo->get_active_text();

            if ($choice) {

                # Import the IV
                @categoryList = $self->getEditHash_listIV('categoryList');

                push (@categoryList, $choice);

                # Refresh the textview
                my $buffer = $textView->get_buffer();
                $buffer->set_text(join("\n", @categoryList));

                # Store the list in the IV
                $self->ivAdd('editHash', 'categoryList', \@categoryList);
            }
        });

        my $button2 = $self->addButton($grid, 'Remove', 'Remove the selected category', undef,
            6, 8, 11, 12);
        $button2->signal_connect('clicked' => sub {

            my (
                $choice, $index, $buffer,
                @categoryList,
            );

            $choice = $combo->get_active_text();

            if ($choice) {

                # Import the IV
                @categoryList = $self->getEditHash_listIV('categoryList');

                # If $choice is present in the category list, remove it
                if (@categoryList) {

                    OUTER: for (my $count = 0; $count < scalar @categoryList; $count++) {

                        if ($categoryList[$count] eq $choice) {

                            $index = $count;
                            last OUTER;
                        }
                    }

                    if (defined $index) {

                        splice (@categoryList, $index, 1);
                    }
                }

                # Refresh the textview
                my $buffer = $textView->get_buffer();
                $buffer->set_text(join("\n", @categoryList));

                # Store the list in the IV
                $self->ivAdd('editHash', 'categoryList', \@categoryList);
            }
        });

        # Tab complete
        return 1;
    }

    sub adjectivesTab {

        # Adjectives tab
        #
        # 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 . '->adjectivesTab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Adjectives');

        # Adjectives
        $self->addLabel($grid, '<b>Adjectives</b>',
            0, 6, 0, 2);

        $self->addLabel($grid, '<i>List of normal adjectives</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'adjList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        $self->addLabel($grid, '<i>List of pseudo-adjectives</i>',
            1, 12, 6, 8);
        $self->addTextView($grid, 'pseudoAdjList', TRUE,
            1, 12, 8, 10,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub unknownsTab {

        # Unknowns tab
        #
        # 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 . '->unknownsTab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Unknowns');

        # Unknowns
        $self->addLabel($grid, '<b>Unknowns</b>',
            0, 6, 0, 2);

        $self->addLabel($grid, '<i>List of unrecognised words</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'unknownWordList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Quest;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Quest')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Name', 'Points'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->solutionTab();
        $self->commentsTab();
        $self->privateDataTab(       # Inherited from GA::Generic::EditWin
            'privateHash',
            '_Private data',
            'Private data hash',
            'Additional data associated with this quest can be stored here',
        );

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object. In this case, we have to
        #   update the quest counts stored in the parent world profile
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the quest counts in the current world profile and all of its character profiles
            $self->session->currentWorld->updateQuestStats($self->session);

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid  - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Name
        $self->addLabel($grid, '<b>Name</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Mission (if any)',
            1, 3, 2, 3);
        @comboList = sort {lc($a) cmp lc($b)} ($self->session->currentWorld->ivKeys('missionHash'));
        $self->addComboBox($grid, 'missionName', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);

        # Points
        $self->addLabel($grid, '<b>Points</b>',
            7, 12, 0, 1);
        $self->addLabel($grid, 'Quest points',
            7, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'questPoints', 'float', undef, undef,
            9, 12, 1, 2);       # (Negative/fractional values allowed)
        $self->addLabel($grid, 'Quest XP',
            7, 9, 2, 3);
        $self->addEntryWithIcon($grid, 'questXP', 'float', undef, undef,
            9, 12, 2, 3);
        $self->addLabel($grid, 'Quest cash',
            7, 9, 3, 4);
        $self->addEntryWithIcon($grid, 'questCash', 'float', undef, undef,
            9, 12, 3, 4);

        # Tab complete
        return 1;
    }

    sub solutionTab {

        # Solution tab
        #
        # 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 . '->solutionTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Solution',
            ['Quest solutions'],
        );

        # Quest solutions
        $self->addLabel($grid, '<b>Quest solutions</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>Add your notes about the quest solution here (use missions to automate quests)</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'solutionList', TRUE,
            1, 12, 4, 10,
            TRUE, FALSE, FALSE, FALSE,   # Treat as a list, don't remove empty lines or whitespace
        );

        # Tab complete
        return 1;
    }

    sub commentsTab {

        # Comments tab
        #
        # 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 . '->commentsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Comments',
            ['Quest comments'],
        );

        # Quest comments
        $self->addLabel($grid, '<b>Quest comments</b>',
            0, 12, 0, 2);
        $self->addLabel($grid, '<i>Add your notes about the quest here</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'commentList', TRUE,
            1, 12, 4, 10,
            TRUE, FALSE, FALSE, FALSE,    # Treat as a list, don't remove empty lines or whitespace
        );

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Regionmap;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Regionmap')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['General properties'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->sizesTab();
        $self->objectsTab();
        $self->countsTab();
        $self->boundariesTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the save can't be performed
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($blockWidth, $blockHeight, $roomWidth, $roomHeight, $failFlag);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # If new gridblock/room sizes have been specified, must check that the room is smaller
            #   than a gridblock
            if (
                $self->ivExists('editHash', 'blockWidthPixels')
                || $self->ivExists('editHash', 'roomWidthPixels')
            ) {
                $blockWidth = $self->getEditHash_scalarIV('blockWidthPixels');
                $roomWidth = $self->getEditHash_scalarIV('roomWidthPixels');

                if ($roomWidth >= $blockWidth) {

                    $failFlag = TRUE;
                }
            }

            if (! $failFlag) {

                if (
                    $self->ivExists('editHash', 'blockHeightPixels')
                    || $self->ivExists('editHash', 'roomHeightPixels')
                ) {
                    $blockHeight = $self->getEditHash_scalarIV('blockHeightPixels');
                    $roomHeight = $self->getEditHash_scalarIV('roomHeightPixels');

                    if ($roomHeight >= $blockHeight) {

                        $failFlag = TRUE;
                    }
                }
            }

            if ($failFlag) {

                # Ignore all changes to the room/block width/height
                $self->ivDelete('editHash', 'blockWidthPixels');
                $self->ivDelete('editHash', 'roomWidthPixels');
                $self->ivDelete('editHash', 'blockHeightPixels');
                $self->ivDelete('editHash', 'roomHeightPixels');
            }

            # If the map has been resized, work out whether any of rooms, exits and labels are now
            #   outside it
            if (! $self->checkMap()) {

                # If the calling function was $self->buttonOK, reset the window rather than closing
                #   it
                return undef;
            }

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # Calculate new IV values for the regionmap width and height
            $self->editObj->{mapWidthPixels}
                = $self->editObj->{gridWidthBlocks} * $self->editObj->{blockWidthPixels};
            $self->editObj->{mapHeightPixels}
                = $self->editObj->{gridHeightBlocks} * $self->editObj->{blockHeightPixels};

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Redraw this region in all Automapper windows using this world model
            $self->session->worldModelObj->updateRegion($self->editObj->name);

            if ($failFlag) {

                $self->showMsgDialogue(
                    'Save changes',
                    'warning',
                    'You specified a room that was bigger than its gridblock; their sizes have not'
                    . ' been changed',
                    'ok',
                );
            }
        }

        return 1;
    }

    # Standard callbacks

    sub buttonOK {

        # 'OK' button callback. For this 'edit' window, if the call to $self->saveChanges returns
        #   'undef', we need to reset the window, rather than closing it
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $number;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->buttonOK', @_);
        }

        # Save changes
        if (! $self->saveChanges()) {

            # Reset the window, rather than closing it. Remove all the tabs
            $number = $self->notebook->get_n_pages();
            if ($number) {

                for (my $count = 0; $count < $number; $count++) {

                    $self->notebook->remove_page(0);
                }
            }

            # Re-draw all the tabs
            $self->setupNotebook();
            # Render the changes
            $self->winShowAll($self->_objClass . '->buttonOK');

        } else {

            # Close the window
            $self->winDestroy();
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -   The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # General properties
        $self->addLabel($grid, '<b>General properties</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, 'Region name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'World model number',
            1, 3, 2, 3);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Current level',
            1, 3, 3, 4);
        $self->addEntry($grid, 'currentLevel', FALSE,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Highest occupied level',
            1, 3, 4, 5);
        $self->addEntry($grid, 'highestLevel', FALSE,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Lowest occupied level',
            1, 3, 5, 6);
        $self->addEntry($grid, 'lowestLevel', FALSE,
            3, 6, 5, 6);

        $self->addLabel($grid, '<u>Exits</u>',
            1, 6, 6, 7);
        $self->addLabel($grid, 'Exit drawing mode',
            1, 3, 7, 8);
        $self->addEntry($grid, 'drawExitMode', FALSE,
            3, 6, 7, 8);
        $self->addCheckButton($grid, 'Draw exit ornaments', 'drawOrnamentsFlag', FALSE,
            1, 6, 8, 9);

        $self->addLabel($grid, '<u>Colour scheme</u>',
            1, 6, 9, 10);
        $self->addLabel($grid, 'Applied scheme',
            1, 3, 10, 11);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 10, 11);
        if (! defined $self->editObj->regionScheme) {
            $entry->set_text('default');
        } else {
            $entry->set_text($self->editObj->regionScheme);
        }

        # Right column
        $self->addLabel($grid, 'Current magnification',
            7, 9, 1, 2);
        $self->addEntry($grid, 'magnification', FALSE,
            9, 12, 1, 2,
            4, 4);
        $self->addLabel($grid, 'Map position X',
            7, 9, 2, 3);
        $self->addEntry($grid, 'scrollXPos', FALSE,
            9, 12, 2, 3,
            4, 4);
        $self->addLabel($grid, 'Map position Y',
            7, 9, 3, 4);
        $self->addEntry($grid, 'scrollYPos', FALSE,
            9, 12, 3, 4,
            4, 4);
        $self->addCheckButton($grid, 'Maximum zoom out X', 'maxZoomOutXFlag', FALSE,
            7, 12, 4, 5);
        $self->addCheckButton($grid, 'Maximum zoom out Y', 'maxZoomOutYFlag', FALSE,
            7, 12, 5, 6);

        $self->addLabel($grid, '<u>Obscured exits</u>',
            7, 12, 6, 7);

        $self->addCheckButton($grid, 'Obscure unimportant exits', 'obscuredExitFlag', FALSE,
            7, 12, 7, 8);
        $self->addCheckButton(
            $grid,
            'Auto-redraw unobscured exits', 'obscuredExitRedrawFlag', FALSE,
            7, 12, 8, 9);
        $self->addLabel($grid, 'Radius of unobscured area',
            7, 9, 9, 10);
        $self->addEntry($grid, 'obscuredExitRadius', FALSE,
            9, 12, 9, 10);
        $self->addLabel($grid, 'Maximum radius (blocks)',
            7, 8, 10, 11);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            9, 12, 10, 11);
        $entry2->set_text($self->session->worldModelObj->maxObscuredExitRadius);

        # Tab complete
        return 1;
    }

    sub sizesTab {

        # Sizes tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $wmObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->sizesTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Sizes',
            ['Current sizes'],
        );

        # Import the session's GA::Obj::WorldModel
        $wmObj = $self->session->worldModelObj;

        # Current sizes
        $self->addLabel($grid, '<b>Current sizes</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Size of the regionmap, in gridblocks (use odd numbers; minimum value is 5)</i>',
            1, 12, 1, 2,
        );

        $self->addLabel($grid, 'Width (x axis)',
            1, 3, 2, 3);
        $self->addEntryWithIcon(
            $grid,
            'gridWidthBlocks',
            'odd',
            5,
            $wmObj->maxGridWidthBlocks,
            3, 6, 2, 3,
        );

        $self->addLabel($grid, 'Maximum width',
            7, 9, 2, 3);
        my $entry = $self->addEntry($grid, undef, FALSE,
            9, 12, 2, 3);
        $entry->set_text($wmObj->maxGridWidthBlocks);

        $self->addLabel($grid, 'Height (y axis)',
            1, 3, 3, 4);
        $self->addEntryWithIcon(
            $grid,
            'gridHeightBlocks',
            'odd',
            5,
            $wmObj->maxGridHeightBlocks,
            3, 6, 3, 4,
        );

        $self->addLabel($grid, 'Maximum height',
            7, 9, 3, 4);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            9, 12, 3, 4);
        $entry2->set_text($wmObj->maxGridHeightBlocks);

        $self->addLabel(
            $grid,
            '<i>Size of a gridblock, in pixels (use odd numbers; minimum value is 11)</i>',
            1, 12, 4, 5,
        );

        $self->addLabel($grid, 'Width',
            1, 3, 5, 6);
        $self->addEntryWithIcon(
            $grid,
            'blockWidthPixels',
            'odd',
            11,
            $wmObj->maxBlockWidthPixels,
            3, 6, 5, 6,
        );

        $self->addLabel($grid, 'Maximum width',
            7, 9, 5, 6);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            9, 12, 5, 6);
        $entry3->set_text($wmObj->maxBlockWidthPixels);

        $self->addLabel($grid, 'Height',
            1, 3, 6, 7);
        $self->addEntryWithIcon(
            $grid,
            'blockHeightPixels',
            'odd',
            11,
            $wmObj->maxBlockHeightPixels,
            3, 6, 6, 7,
        );

        $self->addLabel($grid, 'Maximum height',
            7, 9, 6, 7);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            9, 12, 6, 7);
        $entry4->set_text($wmObj->maxBlockHeightPixels);

        $self->addLabel(
            $grid,
            '<i>Size of a room, in pixels (use odd numbers; minimum value is 5)</i>',
            1, 12, 7, 8,
        );

        $self->addLabel($grid, 'Width',
            1, 3, 8, 9);
        $self->addEntryWithIcon(
            $grid,
            'roomWidthPixels',
            'odd',
            5,
            $wmObj->maxRoomWidthPixels,
            3, 6, 8, 9,
        );

        $self->addLabel($grid, 'Maximum width',
            7, 9, 8, 9);
        my $entry5 = $self->addEntry($grid, undef, FALSE,
            9, 12, 8, 9);
        $entry5->set_text($wmObj->maxRoomWidthPixels);

        $self->addLabel($grid, 'Height',
            1, 3, 9, 10);
        $self->addEntryWithIcon(
            $grid,
            'roomHeightPixels',
            'odd',
            5,
            $wmObj->maxRoomHeightPixels,
            3, 6, 9, 10,
        );

        $self->addLabel($grid, 'Maximum height',
            7, 8, 9, 10);
        my $entry6 = $self->addEntry($grid, undef, FALSE,
            9, 12, 9, 10);
        $entry6->set_text($wmObj->maxRoomHeightPixels);

        $self->addLabel(
            $grid,
            '<i>Current size of the regionmap, in pixels (use the \'save\' button to update these'
            . ' values)</i>',
            1, 12, 10, 11,
        );
        $self->addLabel($grid, 'Width',
            1, 3, 11, 12);
        $self->addEntry($grid, 'mapWidthPixels', FALSE,
            3, 6, 11, 12);
        $self->addLabel($grid, 'Height',
            7, 9, 11, 12);
        $self->addEntry($grid, 'mapHeightPixels', FALSE,
            9, 12, 11, 12);

        # Tab complete
        return 1;
    }

    sub objectsTab {

        # Objects tab - called by $self->setupNotebook
        #
        # 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 . '->objectsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Objects');

        # Add tabs to the inner notebook
        $self->objects1Tab($innerNotebook);
        $self->objects2Tab($innerNotebook);
        $self->objects3Tab($innerNotebook);
        $self->objects4Tab($innerNotebook);
        $self->objects5Tab($innerNotebook);
        $self->objects6Tab($innerNotebook);

        return 1;
    }

    sub objects1Tab {

        # Objects1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->objects1Tab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($innerNotebook, '_Rooms');

        # Rooms
        $self->addLabel($grid, '<b>Rooms</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of rooms drawn in this regionmap</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Model #', 'text',
            'Room tag', 'text',
            'X-pos', 'text',
            'Y-pos', 'text',
            'Z-pos', 'text',
            'No. exits', 'text',
            'Name', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->objects1Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Edit...', 'Edit this room model object', undef,
            1, 3, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($roomNum, $roomObj, $childWinObj);

            ($roomNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $roomNum) {

                $roomObj = $self->session->worldModelObj->ivShow('modelHash', $roomNum);

                # Open an 'edit' window for this room
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::ModelObj::Room',
                    $self,
                    $self->session,
                    'Edit ' . $roomObj->category . ' model object #' . $roomObj->number,
                    $roomObj,
                    FALSE,                          # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'objects1Tab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh',
            'Refresh the list of room model objects',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->objects1Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub objects1Tab_refreshList {

        # Called by $self->objects1Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $wmObj,
            @sortedList, @dataList,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->objects1Tab_refreshList',
                @_,
            );
        }

        # Import the GA::Obj::WorldModel
        $wmObj = $self->session->worldModelObj;
        # Get a list of room model objects in this region, sorted by model number
        @sortedList = sort {$a <=> $b} ($self->editObj->ivValues('gridRoomHash'));

        # Compile the simple list data
        foreach my $roomNum (@sortedList) {

            my $roomObj = $wmObj->ivShow('modelHash', $roomNum);

            push (@dataList,
                $roomNum,
                $roomObj->roomTag,
                $roomObj->xPosBlocks,
                $roomObj->yPosBlocks,
                $roomObj->zPosBlocks,
                $roomObj->ivNumber('sortedExitList'),
                $roomObj->name,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub objects2Tab {

        # Objects2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->objects2Tab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($innerNotebook, 'Room _tags');

        # Room tags
        $self->addLabel($grid, '<b>Room tags</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of room tags drawn in this regionmap</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Room #', 'text',
            'Room tag', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->objects2Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Refresh', 'Refresh the list of room tags', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->objects2Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub objects2Tab_refreshList {

        # Called by $self->objects2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $wmObj,
            @roomList, @shortList, @sortedList, @dataList,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->objects2Tab_refreshList',
                @_,
            );
        }

        # Import the GA::Obj::WorldModel
        $wmObj = $self->session->worldModelObj;
        # Get a list of room model objects in this region
        @roomList = $self->editObj->ivValues('gridRoomTagHash');
        # Whittle this down to a list of rooms with room tags
        foreach my $roomNum (@roomList) {

            my $roomObj = $wmObj->ivShow('modelHash', $roomNum);

            if ($roomObj->roomTag) {

                push (@shortList, $roomObj);
            }
        }

        # Sort the list by tag
        @sortedList = sort {lc($a->roomTag) cmp lc($b->roomTag)} (@shortList);

        # Compile the simple list data
        foreach my $roomObj (@sortedList) {

            push (@dataList,
                $roomObj->number,
                $roomObj->roomTag,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub objects3Tab {

        # Objects3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->objects3Tab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($innerNotebook, 'Room _guilds');

        # Room guilds
        $self->addLabel($grid, '<b>Room guilds</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of room guilds drawn in this regionmap</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Room #', 'text',
            'Room guild', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->objects3Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Refresh', 'Refresh the list of room guilds', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->objects3Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub objects3Tab_refreshList {

        # Called by $self->objects3Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $wmObj,
            @roomList, @shortList, @sortedList, @dataList,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->objects3Tab_refreshList',
                @_,
            );
        }

        # Import the GA::Obj::WorldModel
        $wmObj = $self->session->worldModelObj;
        # Get a list of room model objects in this region
        @roomList = $self->editObj->ivValues('gridRoomGuildHash');
        # Whittle this down to a list of rooms with room tags
        foreach my $roomNum (@roomList) {

            my $roomObj = $wmObj->ivShow('modelHash', $roomNum);

            if ($roomObj->roomGuild) {

                push (@shortList, $roomObj);
            }
        }

        # Sort the list by tag
        @sortedList = sort {lc($a->roomGuild) cmp lc($b->roomGuild)} (@shortList);

        # Compile the simple list data
        foreach my $roomObj (@sortedList) {

            push (@dataList,
                $roomObj->number,
                $roomObj->roomGuild,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub objects4Tab {

        # Objects4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->objects4Tab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($innerNotebook, '_Exits');

        # Exits
        $self->addLabel($grid, '<b>Exits</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of exits drawn in this regionmap</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Exit #', 'text',
            'Dir', 'text',
            'Map dir', 'text',
            'Dest #', 'text',
            'Twin #', 'text',
            'Shadow #', 'text',
            'Broken exit', 'bool',
            'Region exit', 'bool',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->objects4Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Edit...', 'Edit this exit model object', undef,
            1, 3, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj, $childWinObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

                # Open an 'edit' window for this room
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::Exit',
                    $self,
                    $self->session,
                    'Edit exit model object #' . $exitObj->number,
                    $exitObj,
                    FALSE,                          # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'objects4Tab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }

                # Refresh the simple list
                $self->objects4Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh',
            'Refresh the list of exit model objects',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->objects4Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub objects4Tab_refreshList {

        # Called by $self->objects4Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $wmObj,
            @sortedList, @dataList,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->objects4Tab_refreshList',
                @_,
            );
        }

        # Import the GA::Obj::WorldModel
        $wmObj = $self->session->worldModelObj;
        # Get a list of exit model objects in this region, sorted by model number
        @sortedList = sort {$a <=> $b} ($self->editObj->ivKeys('gridExitHash'));

        # Compile the simple list data
        foreach my $exitNum (@sortedList) {

            my ($exitObj, $mapDir);

            $exitObj = $wmObj->ivShow('exitModelHash', $exitNum);

            if ($exitObj->mapDir) {
                $mapDir = $exitObj->mapDir;
            } else {
                $mapDir = 'unallocatable';
            }

            push (@dataList,
                $exitNum,
                $exitObj->dir,
                $mapDir,
                $exitObj->destRoom,
                $exitObj->twinExit,
                $exitObj->shadowExit,
                $exitObj->brokenFlag,
                $exitObj->regionFlag,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub objects5Tab {

        # Objects5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->objects5Tab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($innerNotebook, 'Exit t_ags');

        # Exit tags
        $self->addLabel($grid, '<b>Exit tags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid, '<i>List of region exits with exit tags drawn in this regionmap</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Exit #', 'text',
            'Exit tag', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->objects5Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Refresh', 'Refresh the list of eixt tags', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->objects5Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub objects5Tab_refreshList {

        # Called by $self->objects5Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $wmObj,
            @exitList, @sortedList, @dataList,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->objects5Tab_refreshList',
                @_,
            );
        }

        # Import the GA::Obj::WorldModel
        $wmObj = $self->session->worldModelObj;
        # Get a list of exit model objects with tags in this region
        foreach my $exitNum ($self->editObj->ivKeys('gridExitTagHash')) {

            push (@exitList, $wmObj->ivShow('exitModelHash', $exitNum));
        }

        # Sort the list by tag
        @sortedList = sort {lc($a->exitTag) cmp lc($b->exitTag)} (@exitList);

        # Compile the simple list data
        foreach my $exitObj (@sortedList) {

            push (@dataList,
                $exitObj->number,
                $exitObj->exitTag,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub objects6Tab {

        # Objects6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->objects6Tab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($innerNotebook, '_Labels');

        # Labels
        $self->addLabel($grid, '<b>Labels</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of map labels drawn in this regionmap</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Label #', 'text',
            'X-pos', 'text',
            'Y-pos', 'text',
            'Level', 'text',
            'Label', 'text',
            'Style', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->objects6Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Refresh', 'Refresh the list of labels', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->objects6Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub objects6Tab_refreshList {

        # Called by $self->objects6Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@sortedList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->objects6Tab_refreshList',
                @_,
            );
        }

        # Get a sorted list of labels in this region
        @sortedList = sort {$a->number <=> $b->number} ($self->editObj->ivValues('gridLabelHash'));

        # Compile the simple list data
        foreach my $labelObj (@sortedList) {

            my $style;

            if (defined $labelObj->style) {
                $style = $labelObj->style;
            } else {
                $style = '<custom style>';
            }

            push (@dataList,
                $labelObj->number,
                $labelObj->xPosPixels,
                $labelObj->yPosPixels,
                $labelObj->level,
                $labelObj->name,
                $style,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub countsTab {

        # Counts tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->countsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Counts',
            ['Living/non-living counts'],
        );

        # Living/non-living counts
        $self->addLabel($grid, '<b>Living/non-living counts</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Count of living/non-living objects in each room, the last time it was visited</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Room #', 'text',
            'Living', 'int',
            'Non-living', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->countsTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Refresh', 'Refresh the list of rooms', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->countsTab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub countsTab_refreshList {

        # Called by $self->countsTab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @sortedList, @dataList,
            %livingHash, %nonLivingHash, %combHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->countsTab_refreshList', @_);
        }

        # Import two hashes (each has room numbers as its keys)...
        %livingHash = $self->editObj->livingCountHash;
        %nonLivingHash = $self->editObj->nonLivingCountHash;
        # ...and combine them, giving us a hash with one key-value pair for each room that exists in
        #   either (or both) hashes
        %combHash = (%livingHash, %nonLivingHash);

        # Get a sorted list of rooms
        @sortedList = sort {$a <=> $b} (keys %combHash);

        # Compile the simple list data
        foreach my $number (@sortedList) {

            push (@dataList,
                $number,
                $livingHash{$number},
                $nonLivingHash{$number},
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub boundariesTab {

        # Boundaries tab - called by $self->setupNotebook
        #
        # 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 . '->boundariesTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Region paths');

        # Add tabs to the inner notebook
        $self->boundaries1Tab($innerNotebook);
        $self->boundaries2Tab($innerNotebook);
        $self->boundaries3Tab($innerNotebook);
        $self->boundaries4Tab($innerNotebook);

        return 1;
    }

    sub boundaries1Tab {

        # Boundaries1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->boundaries1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Region exits'],
        );

        # Region exits
        $self->addLabel($grid, '<b>Region exits</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid, '<i>List of exits in this regionmap that lead to another region</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Exit #', 'int',
            'Direction', 'text',
            'Room #', 'int',
            'Leads to region', 'text',
            'Leads to room #', 'text',
            'Twin exit #', 'text',
            'Super-region exit', 'bool',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the simple list
        $self->boundaries1Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        $self->boundaries1Tab_addButtons($grid, $slWidget, scalar (@columnList / 2));

        # Tab complete
        return 1;
    }

    sub boundaries1Tab_addButtons {

        # Called by $self->boundaries1Tab and ->boundaries2Tab to add editing buttons below the
        #   GA::Obj::SimpleList
        #
        # Expected arguments
        #   $grid           - The Gtk3::Grid for this tab
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns in the simple list
        #
        # Optional arguments
        #   $superFlag      - Flag set to TRUE when called by $self->boundaries2Tab; only
        #                       super-region exits are listed. Otherwise set to 'undef'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $slWidget, $columns, $superFlag, $check) = @_;

        # Check for improper arguments
        if (! defined $grid || ! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->boundaries1Tab_addButtons',
                @_,
            );
        }

        my $button = $self->addButton(
            $grid, 'Edit...', 'Edit the selected exit model object', undef,
            1, 3, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj, $childWinObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);
                if ($exitObj) {

                    # Open up an 'edit' window to edit the exit
                    $childWinObj = $self->createFreeWin(
                        'Games::Axmud::EditWin::Exit',
                        $self,
                        $self->session,
                        'Edit exit model object #' . $exitObj->number,
                        $exitObj,
                        FALSE,                          # Not temporary
                    );
                }

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'boundaries1Tab_refreshList',
                        [$slWidget, $columns, $superFlag],
                    );
                }
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Recalculate region paths for exit',
            'Recalculate region paths to/from the selected exit',
            undef,
            3, 8, 10, 11,
            TRUE);          # Irreversible
        $button2->signal_connect('clicked' => sub {

            my ($exitNum, $wmObj, $exitObj, $roomObj, $regionObj, $regionmapObj, $number, $msg);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $wmObj = $self->session->worldModelObj;
                $exitObj = $wmObj->ivShow('exitModelHash', $exitNum);
                if ($exitObj) {

                    # Get the parent regionmap
                    $roomObj = $wmObj->ivShow('modelHash', $exitObj->parent);
                    $regionObj = $wmObj->ivShow('modelHash', $roomObj->parent);
                    $regionmapObj = $wmObj->ivShow('regionmapHash', $regionObj->name);

                    # Recalculate paths from the selected exit
                    $number = $wmObj->recalculateSpecificPaths(
                        $self->session,
                        $regionmapObj,
                        $exitObj,
                    );

                    # If the paths were up-to-date, it may appear that nothing has happened, so show
                    #   a confirmation
                    if (! $number) {
                        $msg = 'no paths found)';            # Also catch 'undef' return value
                    } elsif ($number == 1) {
                        $msg = '1 path found)';
                    } else {
                        $msg = $number . ' paths found)';
                    }

                    $self->showMsgDialogue(
                        'Recalculate paths',
                        'info',
                        'Region paths for exit #' . $exitNum . ' recalculated (' . $msg,
                        'ok',
                    );
                }

                # Refresh the simple list
                $self->boundaries1Tab_refreshList($slWidget, $columns, $superFlag);
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Mark as super-region exit',
            'Make the selected exit a super-region exit',
            undef,
            8, 12, 10, 11,
            TRUE);          # Irreversible
        $button3->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

                # Mark the exit as a super-region exit, and instruct the world model to update its
                #   Automapper windows
                $self->session->worldModelObj->setSuperRegionExit(
                    $self->session,
                    TRUE,       # Update Automapper windows now
                    $exitObj,
                    FALSE,      # Not an exclusive super-region exit
                );

                # Let the world model process any necessary changes (although there are unlikely to
                #   be any because of this button)
                $self->session->worldModelObj->updateRegionPaths($self->session);

                # Refresh the simple list
                $self->boundaries1Tab_refreshList($slWidget, $columns, $superFlag);
            }
        });

        my $button4 = $self->addButton(
            $grid,
            'Mark as exclusive super-region exit',
            'Make the selected exit an exclusive super-region exit',
            undef,
            1, 4, 11, 12,
            TRUE);          # Irreversible
        $button4->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

                # Mark the exit as an exclusive super-region exit, and instruct the world model to
                #   update its Automapper windows
                $self->session->worldModelObj->setSuperRegionExit(
                    $self->session,
                    TRUE,       # Update Automapper windows now
                    $exitObj,
                    TRUE,       # Is an exclusive super-region exit
                );

                # Let the world model process any necessary changes (although there are unlikely to
                #   be any because of this button)
                $self->session->worldModelObj->updateRegionPaths($self->session);

                # Refresh the simple list
                $self->boundaries1Tab_refreshList($slWidget, $columns, $superFlag);
            }
        });

        my $button5 = $self->addButton(
            $grid,
            'Mark as normal region exit',
            'Make the selected exit a normal region exit',
            undef,
            4, 8, 11, 12,
            TRUE);              # Irreversible
        $button5->signal_connect('clicked' => sub {

            my ($exitNum, $exitObj);

            ($exitNum) = $self->getSimpleListData($slWidget, 0);
            if (defined $exitNum) {

                $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

                # Convert the super-region exit to a normal region exit and instruct the world model
                #   to update its Automapper windows
                $self->session->worldModelObj->restoreSuperRegionExit(
                    TRUE,       # Update Automapper windows now
                    $exitObj,
                );

                # Let the world model process any necessary changes (it's fairly likely that there
                #   will be some because of this button)
                $self->session->worldModelObj->updateRegionPaths($self->session);

                # Refresh the simple list
                $self->boundaries1Tab_refreshList($slWidget, $columns, $superFlag);
            }
        });

        my $button6 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of exit model objects',
            undef,
            8, 12, 11, 12
        );
        $button6->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->boundaries1Tab_refreshList($slWidget, $columns, $superFlag);
        });

        return 1;
    }

    sub boundaries1Tab_refreshList {

        # Called by $self->boundaries1Tab, >boundaries2Tab and ->boundaries1Tab_addButtons refresh
        #   the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Optional arguments
        #   $superFlag  - Flag set to TRUE when called by $self->boundaries2Tab (perhaps via
        #                   ->boundaries1Tab_addButtons); only super-region exits are listed.
        #                   Otherwise set to 'undef'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $superFlag, $check) = @_;

        # Local variables
        my (@sortedList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->boundaries1Tab_refreshList',
                @_,
            );
        }

        # Get a list of boundary exit objects in this region, sorted by exit number
        @sortedList = sort {$a <=> $b} ($self->editObj->ivKeys('regionExitHash'));

        # Compile the simple list data
        OUTER: foreach my $exitNum (@sortedList) {

            my ($otherRegionNum, $otherRegionObj, $exitObj);

            $otherRegionNum = $self->editObj->ivShow('regionExitHash', $exitNum);
            $otherRegionObj = $self->session->worldModelObj->ivShow('modelHash', $otherRegionNum);
            $exitObj = $self->session->worldModelObj->ivShow('exitModelHash', $exitNum);

            # In ->boundaries2Tab, only display super-region exits
            if (! $superFlag || $exitObj->superFlag) {

                push (@dataList,
                    $exitNum,
                    $exitObj->dir,
                    $exitObj->parent,
                    $otherRegionObj->name,
                    $exitObj->destRoom,
                    $exitObj->twinExit,
                    $exitObj->superFlag,
                );
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub boundaries2Tab {

        # Boundaries2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->boundaries2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Super-region exits'],
        );

        # Super-region exits
        $self->addLabel($grid, '<b>Super-region exits</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid, '<i>List of region exits in this regionmap which are given priority in'
            . ' pathfinding operations</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Exit #', 'int',
            'Direction', 'text',
            'Room #', 'int',
            'Leads to region', 'text',
            'Leads to room #', 'text',
            'Twin exit #', 'text',
            'Super-region exit', 'bool',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the simple list. The TRUE argument means the calling function is this function,
        #   not $self->boundaries1Tab
        $self->boundaries1Tab_refreshList($slWidget, scalar (@columnList / 2), TRUE);

        # Add editing buttons
        $self->boundaries1Tab_addButtons($grid, $slWidget, scalar (@columnList / 2), TRUE);

        # Tab complete
        return 1;
    }

    sub boundaries3Tab {

        # Boundaries3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->boundaries3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Region paths'],
        );

        # Region paths
        $self->addLabel($grid, '<b>Region paths</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid, '<i>List of shortest paths between region exits in this regionmap</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Start exit #', 'int',
            'Stop exit #', 'int',
            'Steps', 'text',
            'Path', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->boundaries3Tab_refreshList($slWidget, scalar (@columnList / 2), 'regionPathHash');

        # Add editing buttons
        my $button = $self->addButton(
            $grid,
            'Recalculate region paths',
            'Recalculate region paths for this regionmap',
            undef,
            1, 4, 10, 11,
            TRUE,               # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($number, $number2);

            # Recalcualate region paths
            $self->session->worldModelObj->recalculateRegionPaths(
                $self->session,
                $self->editObj,
            );

            # Refresh the simple list
            $self->boundaries3Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'regionPathHash',
            );

            # If the paths were up-to-date, it may appear that nothing has happened, so show a
            #   confirmation
            $number = $self->editObj->ivPairs('regionPathHash');
            $number2 = $self->editObj->ivPairs('safeRegionPathHash');

            $self->showMsgDialogue(
                'Recalculate paths',
                'info',
                'Region paths recalculated (paths: ' . $number . ', safe paths: ' . $number2 . ')',
                'ok',
            );
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of region paths',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->boundaries3Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'regionPathHash',
            );
        });

        # Tab complete
        return 1;
    }

    sub boundaries3Tab_refreshList {

        # Called by both $self->boundaries3Tab and $self->boundaries4Tab to refresh their
        #   GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $iv         - The IV being accessed - 'regionPathHash' or 'safeRegionPathHash'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $iv, $check) = @_;

        # Local variables
        my (
            $wmObj,
            @sortedList, @dataList,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $iv || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->boundaries3Tab_refreshList',
                @_,
            );
        }

        # Import the world model object (for convenience)
        $wmObj = $self->session->worldModelObj;

        # Get a list of GA::Obj::RegionPath objects in this region, sorted by initial/final exit
        #   number
        @sortedList = sort {
            if ($a->startExit == $b->startExit) {
                $a->stopExit <=> $b->stopExit;
            } else {
                $a->startExit <=> $b->startExit;
            }
        } ($self->editObj->ivValues($iv));

        # Compile the simple list data
        foreach my $regionPathObj (@sortedList) {

            my (@cmdList);

            foreach my $exitNum ($regionPathObj->exitList) {

                my ($exitObj, $cmdSequence);

                $exitObj = $wmObj->ivShow('exitModelHash', $exitNum);

                # If the world model is using assisted moves, display them here; otherwise, just
                #   display each exit's nominal directions
                if ($wmObj->assistedMovesFlag) {

                    $cmdSequence = $exitObj->getAssisted($self->session);
                    if ($cmdSequence) {
                        push (@cmdList, $cmdSequence);
                    } else {
                        push (@cmdList, $exitObj->dir);
                    }

                } else {

                    push (@cmdList, $exitObj->dir);
                }
            }

            push (@dataList,
                $regionPathObj->startExit,
                $regionPathObj->stopExit,
                $regionPathObj->roomCount,
                join($axmud::CLIENT->cmdSep, @cmdList),
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub boundaries4Tab {

        # Boundaries4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->boundaries4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Region safe paths'],
        );

        # Region safe paths
        $self->addLabel($grid, '<b>Region safe paths</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of shortest paths between region exits, avoiding rooms with hazardous room'
            . ' flags</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Start exit #', 'int',
            'Stop exit #', 'int',
            'Steps', 'text',
            'Path', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->boundaries3Tab_refreshList(
            $slWidget,
            scalar (@columnList / 2),
            'safeRegionPathHash',
        );

        # Add editing buttons
        my $button = $self->addButton(
            $grid,
            'Recalculate region paths',
            'Recalculate region paths for this regionmap',
            undef,
            1, 4, 10, 11,
            TRUE,               # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($number, $number2);

            # Recalculate region paths
            $self->session->worldModelObj->recalculateRegionPaths(
                $self->session,
                $self->editObj,
            );

            # Refresh the simple list
            $self->boundaries3Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'safeRegionPathHash',
            );

            # If the paths were up-to-date, it may appear that nothing has happened, so show a
            #   confirmation
            $number = $self->editObj->ivPairs('regionPathHash');
            $number2 = $self->editObj->ivPairs('safeRegionPathHash');

            $self->showMsgDialogue(
                'Recalculate paths',
                'info',
                'Region paths recalculated (paths: ' . $number . ', safe paths: ' . $number2 . ')',
                'ok',
            );
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of region paths',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->boundaries3Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'safeRegionPathHash',
            );
        });

        # Tab complete
        return 1;
    }

    # Support functions

    sub checkMap {

        # Called by $self->saveChanges
        # Checks whether $self->editObj's map has been resized and, if so, works out whether any
        #   of the rooms, exits and labels are now outside its boundaries. If so, displays an error
        #   message (the calling function then doesn't save any values)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if any rooms, exits or labels are now outside the
        #       boundaries of the map
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $wmObj, $gridWidthBlocks, $gridHeightBlocks, $blockWidthPixels, $blockHeightPixels,
            $failFlag,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkMap', @_);
        }

        # If the map has been resized, work out whether any of rooms, exits and labels are now
        #   outside it
        if (
            $self->ivExists('editHash', 'gridWidthBlocks')
            || $self->ivExists('editHash', 'gridHeightBlocks')
            || $self->ivExists('editHash', 'blockWidthPixels')
            || $self->ivExists('editHash', 'blockHeightPixels')
        ) {
            # Import the new size of the map
            $gridWidthBlocks = $self->getEditHash_scalarIV('gridWidthBlocks');
            $gridHeightBlocks = $self->getEditHash_scalarIV('gridHeightBlocks');
            $blockWidthPixels = $self->getEditHash_scalarIV('blockWidthPixels');
            $blockHeightPixels = $self->getEditHash_scalarIV('blockHeightPixels');

            # Import the world model object
            $wmObj = $self->session->worldModelObj;

            # Check every room in the region (any exits must be attached to a room, so we don't
            #   need to explicitly check them)
            foreach my $roomNum ($self->editObj->ivValues('gridRoomHash')) {

                my $roomObj = $wmObj->ivShow('modelHash', $roomNum);

                if (
                    $roomObj->xPosBlocks >= $gridWidthBlocks
                    || $roomObj->yPosBlocks >= $gridHeightBlocks
                ) {
                    # Map is too small now
                    $failFlag = TRUE;
                }
            }

            # Check every label in the region
            if (! $failFlag) {

                foreach my $labelObj ($self->editObj->ivValues('gridLabelHash')) {

                    if (
                        $labelObj->xPosPixels >= ($gridWidthBlocks * $blockWidthPixels)
                        || $labelObj->yPosPixels >= ($gridHeightBlocks * $blockHeightPixels)
                    ) {
                        $failFlag = TRUE;
                    }
                }
            }

            if ($failFlag) {

                # Cancel the map size changes (leaving any other changes intact)
                $self->ivDelete('editHash', 'gridWidthBlocks');
                $self->ivDelete('editHash', 'gridHeightBlocks');
                $self->ivDelete('editHash', 'blockWidthPixels');
                $self->ivDelete('editHash', 'blockHeightPixels');
                $self->ivDelete('editHash', 'roomWidthPixels');
                $self->ivDelete('editHash', 'roomHeightPixels');

                # Warn the user
                $self->showMsgDialogue(
                    'Map too small',
                    'error',
                    'Your resized map is too small to contain all the rooms and labels within it.'
                    . ' The map has been restored to its original size.',
                    'ok',
                );

                # Return 'undef' to stop the 'edit' window from being closed/reset
                return undef;
            }
        }

        # The map size is OK
        return 1;
    }

    ##################
    # Methods

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::RegionScheme;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::RegionScheme')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Region scolour scheme settings'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->coloursTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # 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 . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Redraw all regions using this region scheme, in all automapper windows
            foreach my $regionmapObj ($self->session->worldModelObj->ivValues('regionmapHash')) {

                if (
                    (
                        ! defined $regionmapObj->regionScheme
                        && $self->editObj eq $self->session->worldModelObj->defaultSchemeObj
                    ) || (
                        defined $regionmapObj->regionScheme
                        && $regionmapObj->regionScheme eq $self->editObj
                    )
                ) {
                    $self->session->worldModelObj->updateRegion($regionmapObj->name);
                }
            }
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Region colour scheme settings
        $self->addLabel($grid, '<b>Region colour scheme settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', 0,
            3, 6, 1, 2);

        # Add a simple list
        @columnList = (
            'Used by regions', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            7, 12, 1, 11);

        # Initialise the list
        $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Region',
            1, 3, 11, 12);

        @comboList = sort {lc($a) cmp lc($b)} (
            $self->session->worldModelObj->ivKeys('regionmapHash')
        );
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 11, 12);

        my $button = $self->addButton($grid,
            'Apply scheme', 'Apply this scheme to the selected region', undef,
            6, 8, 11, 12,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my $regionName = $combo->get_active_text();
            if ($regionName ne '') {

                # Detach the region scheme from this region. The TRUE value means to redraw the
                #   region immediately
                $self->session->worldModelObj->attachRegionScheme(
                    TRUE,
                    $self->editObj->name,
                    $regionName,
                );

                # Update the simple list
                $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button2 = $self->addButton($grid,
            'Detach scheme', 'Detach this scheme from the selected region', undef,
            8, 10, 11, 12,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            my $regionName = $combo->get_active_text();
            if ($regionName ne '') {

                # Detach the region scheme from this region. The TRUE value means to redraw the
                #   region immediately
                $self->session->worldModelObj->detachRegionScheme(TRUE, $regionName);

                # Update the simple list
                $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button3 = $self->addButton($grid, 'Update', 'Update thislist', undef,
            10, 12, 11, 12);
        $button3->signal_connect('clicked' => sub {

            # Update the simple list
            $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub nameTab_refreshList {

        # Resets the simple list displayed by $self->coloursTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@regionList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab_refreshList', @_);
        }

        foreach my $regionmapObj ($self->session->worldModelObj->ivValues('regionmapHash')) {

            if (
                (
                    ! defined $regionmapObj->regionScheme
                    && $self->editObj eq $self->session->worldModelObj->defaultSchemeObj
                ) || (
                    defined $regionmapObj->regionScheme
                    && $regionmapObj->regionScheme eq $self->editObj->name
                )
            ) {
                push (@regionList, $regionmapObj->name);
            }
        }

        @dataList = sort {lc($a) cmp lc($b)} (@regionList);

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub coloursTab {

        # Colours tab - called by $self->setupNotebook
        #
        # 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 . '->coloursTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Colours');

        # Add tabs to the inner notebook
        $self->colours1Tab($innerNotebook);
        $self->colours2Tab($innerNotebook);
        $self->colours3Tab($innerNotebook);
        $self->colours4Tab($innerNotebook);
        $self->colours5Tab($innerNotebook);
        $self->colours6Tab($innerNotebook);

        return 1;
    }

    sub colours1Tab {

        # Colours1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->colours1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Map colours (1/6)'],
        );

        # Map colours (1/6)
        $self->addLabel($grid, '<b>Map colours (1/6)</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Colours used by the map to draw these region(s)</i>',
            1, 12, 1, 2);

        $self->coloursTab_addRow(
            $grid,
            2,
            'backgroundColour',
            'defaultBackgroundColour',
            'background',
        );

        $self->coloursTab_addRow(
            $grid,
            3,
            'roomColour',
            'defaultRoomColour',
            'room',
        );

        $self->coloursTab_addRow(
            $grid,
            4,
            'roomTextColour',
            'defaultRoomTextColour',
            'room interior text',
        );

        $self->coloursTab_addRow(
            $grid,
            5,
            'selectBoxColour',
            'defaultSelectBoxColour',
            'selection box',
        );

        # Tab complete
        return 1;
    }

    sub coloursTab_addRow {

        # Called by $self->colours1Tab, etc
        # Adds a single row of labels, entry boxes and buttons to allow configuration of a single
        #   colour IV
        #
        # Expected arguments
        #   $grid       - The Gtk3::Grid for this tab
        #   $row        - The number of the row in the Gtk3::Grid displayed in this tab
        #   $iv         - The IV used, e.g. 'backgroundColour'
        #   $defaultIV  - The corresponding default GA::Obj::WorldModel IV, e.g.
        #                   'defaultBackgroundColour'
        #   $labelName  - Which label to use, e.g. 'background'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $row, $iv, $defaultIV, $labelName, $check) = @_;

        # Local variables
        my ($rgbColour, $rgbDefault);

        # Check for improper arguments
        if (
            ! defined $grid || ! defined $row || ! defined $iv || ! defined $defaultIV
            || ! defined $labelName || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->coloursTab_addRow', @_);
        }

        # Initialise vars
        $rgbColour = $self->editObj->$iv;
        $rgbDefault = $self->session->worldModelObj->$defaultIV;

        # Colour
        $self->addLabel($grid, ucfirst($labelName),
            1, 3, $row, ($row + 1));

        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($grid, $rgbColour, undef,
            3, 4, $row, ($row + 1));

        my $entry = $self->addEntry($grid, $iv, FALSE,
            4, 6, $row, ($row + 1), 7, 7);

        my $button = $self->addButton($grid, 'Change', 'Change this colour', undef,
            6, 7, $row, ($row + 1));
        $button->signal_connect('clicked' => sub {

            # Prompt the user to select a new colour, using the existing colour as an initial value
            my $rgbModify = $self->showColourSelectionDialogue(
                'Set ' . $labelName . ' colour',
                $rgbColour,
            );

            if ($rgbModify) {

                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $rgbModify);
                $entry->set_text($rgbModify);
                $rgbColour = $rgbModify;

                # Update IVs
                $self->ivAdd('editHash', $iv, $rgbModify);
            }
        });

        # Default colour
        my $button2 = $self->addButton($grid, 'Use default:', 'Use the default colour', undef,
            8, 9, $row, ($row + 1));
        $button2->signal_connect('clicked' => sub {

            $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $rgbDefault);
            $entry->set_text($rgbDefault);

            # Update IVs
                $self->ivAdd('editHash', $iv, $rgbDefault);
        });

        $self->addSimpleCanvas($grid, $rgbDefault, undef,
            9, 10, $row, ($row + 1));

        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, $row, ($row + 1), 7, 7);
        $entry2->set_text($self->session->worldModelObj->$defaultIV);

        return 1;
    }

    sub colours2Tab {

        # Colours2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->colours2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Map colours (2/6)'],
        );

        # Map colours (2/6)
        $self->addLabel($grid, '<b>Map colours (2/6)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Room border colours used by the map, and their default values'
            . ' (see also next tab)</i>',
            1, 12, 1, 2,
        );

        $self->coloursTab_addRow(
            $grid,
            2,
            'borderColour',
            'defaultBorderColour',
            'room border',
        );

        $self->coloursTab_addRow(
            $grid,
            3,
            'currentBorderColour',
            'defaultCurrentBorderColour',
            'current room (\'update\' mode)',
        );

        $self->coloursTab_addRow(
            $grid,
            4,
            'currentFollowBorderColour',
            'defaultCurrentFollowBorderColour',
            'current room (\'follow\' mode)',
        );

        $self->coloursTab_addRow(
            $grid,
            5,
            'currentWaitBorderColour',
            'defaultCurrentWaitBorderColour',
            'current room (\'wait\' mode)',
        );

        $self->coloursTab_addRow(
            $grid,
            6,
            'currentSelectBorderColour',
            'defaultCurrentSelectBorderColour',
            'current and selected room',
        );

        $self->coloursTab_addRow(
            $grid,
            7,
            'lostBorderColour',
            'defaultLostBorderColour',
            'lost room border',
        );

        $self->coloursTab_addRow(
            $grid,
            8,
            'lostSelectBorderColour',
            'defaultLostSelectBorderColour',
            'selected lost room border',
        );

        # Tab complete
        return 1;
    }

    sub colours3Tab {

        # Colours3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->colours3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Map colours (3/6)'],
        );

        # Map colours (3/6)
        $self->addLabel($grid, '<b>Map colours (3/6)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Room border colours used by the map, and their default values (see also previous'
            . ' tab)</i>',
            1, 12, 1, 2,
        );

        $self->coloursTab_addRow(
            $grid,
            2,
            'ghostBorderColour',
            'defaultGhostBorderColour',
            'ghost room border',
        );

        $self->coloursTab_addRow(
            $grid,
            3,
            'ghostSelectBorderColour',
            'defaultGhostSelectBorderColour',
            'ghost and selected room border',
        );

        $self->coloursTab_addRow(
            $grid,
            4,
            'selectBorderColour',
            'defaultSelectBorderColour',
            'selected room border',
        );

        $self->coloursTab_addRow(
            $grid,
            5,
            'roomAboveColour',
            'defaultRoomAboveColour',
            'room echo (from above)',
        );

        $self->coloursTab_addRow(
            $grid,
            6,
            'roomBelowColour',
            'defaultRoomBelowColour',
            'room echo (from below)',
        );

        # Tab complete
        return 1;
    }

    sub colours4Tab {

        # Colours4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->colours4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Map colours (4/6)'],
        );

        # Map colours (4/6)
        $self->addLabel($grid, '<b>Map colours (4/6)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Exit colours used by the map, and their default values (see also next tab)</i>',
            1, 12, 1, 2);

        $self->coloursTab_addRow(
            $grid,
            2,
            'exitColour',
            'defaultExitColour',
            'exit',
        );

        $self->coloursTab_addRow(
            $grid,
            3,
            'selectExitColour',
            'defaultSelectExitColour',
            'selected exit',
        );

        $self->coloursTab_addRow(
            $grid,
            4,
            'selectExitTwinColour',
            'defaultSelectExitTwinColour',
            'selected exit\'s twin',
        );

        $self->coloursTab_addRow(
            $grid,
            5,
            'selectExitShadowColour',
            'defaultSelectExitShadowColour',
            'selected exit\'s shadow exit',
        );

        $self->coloursTab_addRow(
            $grid,
            6,
            'randomExitColour',
            'defaultRandomExitColour',
            'random exit (2nd colour)',
        );

        $self->coloursTab_addRow(
            $grid,
            7,
            'impassableExitColour',
            'defaultImpassableExitColour',
            'impassable exit',
        );

        # Tab complete
        return 1;
    }

    sub colours5Tab {

        # Colours5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->colours5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Map colours (5/6)'],
        );

        # Map colours (5/6)
        $self->addLabel($grid, '<b>Map colours (5/6)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Exit colours used by the map, and their default values (see also previous tab)</i>',
            1, 12, 1, 2);

        $self->coloursTab_addRow(
            $grid,
            2,
            'mysteryExitColour',
            'defaultMysteryExitColour',
            'mystery exit',
        );

        $self->coloursTab_addRow(
            $grid,
            3,
            'checkedDirColour',
            'defaultCheckedDirColour',
            'checked dir',
        );

        $self->coloursTab_addRow(
            $grid,
            4,
            'dragExitColour',
            'defaultDragExitColour',
            'draggable exit',
        );

        # Tab complete
        return 1;
    }

    sub colours6Tab {

        # Colours6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->colours6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['Map colours (6/6)'],
        );

        # Map colours (6/6)
        $self->addLabel($grid, '<b>Map colours (6/6)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Room tag / room guild / exit tag colours used by the map, and their default'
            . ' values</i>',
            1, 12, 1, 2,
        );

        $self->coloursTab_addRow(
            $grid,
            2,
            'roomTagColour',
            'defaultRoomTagColour',
            'room tag',
        );

        $self->coloursTab_addRow(
            $grid,
            3,
            'selectRoomTagColour',
            'defaultSelectRoomTagColour',
            'selected room tag',
        );

        $self->coloursTab_addRow(
            $grid,
            4,
            'roomGuildColour',
            'defaultRoomGuildColour',
            'room guild',
        );

        $self->coloursTab_addRow(
            $grid,
            5,
            'selectRoomGuildColour',
            'defaultSelectRoomGuildColour',
            'selected room guild',
        );

        $self->coloursTab_addRow(
            $grid,
            6,
            'exitTagColour',
            'defaultExitTagColour',
            'exit tag',
        );

        $self->coloursTab_addRow(
            $grid,
            7,
            'selectExitTagColour',
            'defaultSelectExitTagColour',
            'selected exit tag',
        );

        $self->coloursTab_addRow(
            $grid,
            8,
            'mapLabelColour',
            'defaultMapLabelColour',
            'label (failsafe colour)',
        );

        $self->coloursTab_addRow(
            $grid,
            9,
            'selectMapLabelColour',
            'defaultSelectMapLabelColour',
            'selected label',
        );

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Route;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Route')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->routeTab();

        # Set up the remaining tabs (none exist for this 'edit' window)
#        $self->expandNotebook();

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub routeTab {

        # Route tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $route;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->routeTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Route',
            ['General properties', 'Route'],
        );

        # Left column

        # General properties
        $self->addLabel($grid, '<b>General properties</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Route type',
            1, 3, 1, 2);
        $self->addEntry($grid, 'routeType', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Start room tag',
            1, 3, 2, 3);
        $self->addEntry($grid, 'startRoom', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Stop room tag',
            1, 3, 3, 4);
        $self->addEntry($grid, 'startRoom', FALSE,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Circuit name',
            1, 3, 4, 5);
        $self->addEntry($grid, 'circuitName', FALSE,
            3, 6, 4, 5);

        # Route
        $self->addLabel($grid, '<b>Route</b>',
            0, 12, 5, 6);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            1, 12, 6, 7);
        # (->signal_connect appears below)
        $entry->set_text($self->editObj->route);

        $self->addLabel($grid, 'Number of steps',
            1, 3, 7, 8);
        my $entry2 = $self->addEntry($grid, 'stepCount', FALSE,
            3, 6, 7, 8);

        my $button = $self->addButton(
            $grid,
            'Convert speedwalk command',
            'Convert the speedwalk command to ordinary world commands',
            undef,
            7, 12, 7, 8);
        $button->signal_connect('clicked' => sub {

            my (
                $text,
                @list,
            );

            $text = $entry->get_text();
            @list = $self->session->parseSpeedWalk($text);
            if (@list) {

                $entry->set_text(join($axmud::CLIENT->cmdSep, @list));
            }
        });

        $route = $self->getEditHash_scalarIV('route');
        if (! defined $route || ! $self->session->parseSpeedWalk($route)) {

            $button->set_sensitive(FALSE);
        }

        # Right column
        $self->addCheckButton($grid, 'Hoppable', 'hopFlag', TRUE,
            7, 12, 1, 2);

        $entry->signal_connect('changed' => sub {

            my $text = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                if (index($text, $axmud::CLIENT->constSpeedSigil) == 0) {

                    if (! $self->session->parseSpeedWalk($text)) {

                        $button->set_sensitive(FALSE);
                        $entry->set_icon_from_stock('secondary', 'gtk-no');

                    } else {

                        $button->set_sensitive(TRUE);

                        # Set the IV directly, rather than using ->editHash
                        $self->editObj->ivPoke('route', $text);
                        # ...so that ->stepCount can also be updated
                        $self->editObj->resetStepCount($self->session);
                        $entry2->set_text($self->editObj->stepCount);
                    }

                } else {

                    $button->set_sensitive(FALSE);

                    # Set the IV directly, rather than using ->editHash
                    $self->editObj->ivPoke('route', $text);
                    # ...so that ->stepCount can also be updated
                    $self->editObj->resetStepCount($self->session);
                    $entry2->set_text($self->editObj->stepCount);
                }
            }
        });

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Strip;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Generic::Strip')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

        my ($self, $hBox, $check) = @_;

        # Local variables
        my @emptyList;

        # Check for improper arguments
        if (! defined $hBox || defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableButtons', @_);
            return @emptyList;
        }

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->numberTab();

        # Set up the remaining tabs (none exist for this 'edit' window)
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which type of task is being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Expand the notebook
        $self->settingsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub numberTab {

        # Number tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->numberTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Number',
            ['Strip object settings'],
        );

        # Left column
        $self->addLabel($grid, '<b>Strip object settings</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Number',
            1, 3, 1, 2);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Type',
            1, 3, 2, 3);
        $self->addEntry($grid, 'type', FALSE,
            3, 6, 2, 3);

        $self->addLabel($grid, 'Used in window',
            1, 3, 3, 4);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 3, 4);
        $entry->set_text($self->editObj->winObj->number);

        my $checkButton = $self->addCheckButton($grid, 'Visible', 'visibleFlag', FALSE,
            1, 6, 4, 5);
        my $checkButton2 = $self->addCheckButton($grid, 'Jealous', 'jealousyFlag', FALSE,
            1, 6, 5, 6);

        # Right column
        my $checkButton3 = $self->addCheckButton(
            $grid, 'Expand (use share of empty space)', 'expandFlag', FALSE,
            7, 12, 1, 2);
        my $checkButton4 = $self->addCheckButton(
            $grid, 'Fill (empty space used, not as padding)', 'fillFlag', FALSE,
            7, 12, 2, 3);
        my $checkButton5 = $self->addCheckButton(
            $grid, 'Spacing (use gap between adjacent strips)', 'spacingFlag', FALSE,
            7, 12, 3, 4);
        my $checkButton6 = $self->addCheckButton(
        $grid, 'Strip available in blind mode', 'blindFlag', FALSE,
            7, 12, 4, 5);
        my $checkButton7 = $self->addCheckButton(
        $grid, 'Strip can receive focus', 'allowFocusFlag', FALSE,
            7, 12, 5, 6);

        # Add a button
        my $button = $self->addButton($grid, 'Update', 'Update the displayed settings', undef,
            10, 12, 6, 7);
        $button->signal_connect('clicked' => sub {

            $checkButton->set_active($self->editObj->visibleFlag);
            $checkButton2->set_active($self->editObj->jealousyFlag);
            $checkButton3->set_active($self->editObj->expandFlag);
            $checkButton4->set_active($self->editObj->fillFlag);
            $checkButton5->set_active($self->editObj->spacingFlag);
            $checkButton6->set_active($self->editObj->blindFlag);
            $checkButton7->set_active($self->editObj->allowFocusFlag);
        });

        # Tab complete
        return 1;
    }

    sub settingsTab {

        # Settings tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settingsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Settings',
            ['Initial settings'],
        );

        # Initial settings
        $self->addLabel($grid, '<b>Initial settings</b>',
            0, 12, 0, 1);
            # Bottom section
        $self->addLabel($grid, '<i>Strip object\'s customisation settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, 'initHash', \@columnList,
            1, 12, 2, 12);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Table;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Generic::Table')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

        my ($self, $hBox, $check) = @_;

        # Local variables
        my @emptyList;

        # Check for improper arguments
        if (! defined $hBox || defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableButtons', @_);
            return @emptyList;
        }

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->numberTab();

        # Set up the remaining tabs (none exist for this 'edit' window)
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which type of task is being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Expand the notebook
        $self->settingsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub numberTab {

        # Number tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->numberTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Number',
            ['Table object settings'],
        );

        # Left column
        $self->addLabel($grid, '<b>Table object settings</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Number',
            1, 3, 1, 2);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Type',
            1, 3, 2, 3);
        $self->addEntry($grid, 'type', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Optional name',
            1, 3, 3, 4);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 3, 4);
        my $checkButton = $self->addCheckButton(
            $grid, 'Can be removed from table', 'allowRemoveFlag', FALSE,
            1, 6, 4, 5);
        my $checkButton2 = $self->addCheckButton(
            $grid, 'Can be resized on table', 'allowResizeFlag', FALSE,
            1, 6, 5, 6);

        # Right column
        $self->addLabel($grid, 'Tablezone number',
            7, 9, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            9, 12, 1, 2);
        $entry->set_text($self->editObj->zoneObj->number);

        $self->addLabel($grid, 'Top-left X',
            7, 9, 2, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            9, 12, 2, 3);
        $entry2->set_text($self->editObj->zoneObj->left);

        $self->addLabel($grid, 'Top-left Y',
            7, 9, 3, 4);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            9, 12, 3, 4);
        $entry3->set_text($self->editObj->zoneObj->top);

        $self->addLabel($grid, 'Bottom-right X',
            7, 9, 4, 5);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            9, 12, 4, 5);
        $entry4->set_text($self->editObj->zoneObj->right);

        $self->addLabel($grid, 'Bottom-right Y',
            7, 9, 5, 6);
        my $entry5 = $self->addEntry($grid, undef, FALSE,
            9, 12, 5, 6);
        $entry5->set_text($self->editObj->zoneObj->bottom);

        # Add a button
        my $button = $self->addButton($grid, 'Update', 'Update the displayed settings', undef,
            10, 12, 6, 7);
        $button->signal_connect('clicked' => sub {

            $checkButton->set_active($self->editObj->allowRemoveFlag);
            $checkButton2->set_active($self->editObj->allowResizeFlag);
            $entry->set_text($self->editObj->zoneObj->number);
            $entry2->set_text($self->editObj->zoneObj->left);
            $entry3->set_text($self->editObj->zoneObj->top);
            $entry4->set_text($self->editObj->zoneObj->right);
            $entry5->set_text($self->editObj->zoneObj->bottom);
        });

        # Tab complete
        return 1;
    }

    sub settingsTab {

        # Settings tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settingsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Settings',
            ['Initial settings'],
        );

        # Initial settings
        $self->addLabel($grid, '<b>Initial settings</b>',
            0, 12, 0, 1);
            # Bottom section
        $self->addLabel($grid, '<i>Table object\'s customisation settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, 'initHash', \@columnList,
            1, 12, 2, 12);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Task;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Generic::Task')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Names', 'Description and task help'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook, depending on which type of task is being edited
        #
        # 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 . '->expandNotebook', @_);
        }

        # Some tabs are used, or not, depending on what type of task this is. In addition, some tabs
        #   are inherited from GA::Generic::EditWin. For the benefit of both, set
        #   $self->currentFlag if this task is in the current tasklist
        if ($self->editObj->taskType && $self->editObj->taskType eq 'current') {

            $self->ivPoke('currentFlag', TRUE);
        }

        # Expand the notebook
        $self->settingsTab();
        $self->parametersTab();
        if ($self->currentFlag) {

            $self->actionsTab();
        }

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave
        # Saves any changes made to data stored by the edit object. For current tasks, resets the
        #   task
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my ($colourScheme, $applyFlag);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->saveChanges', @_);
        }

        if ($self->editHash) {

            if ($self->ivExists('editHash', 'colourScheme')) {

                # The colours scheme has changed. After resetting the task, the colour scheme needs
                #   to be applied (or reset, if the new value is 'undef')
                $colourScheme = $self->ivShow('editHash', 'colourScheme');
                $applyFlag = TRUE;

                $self->ivDelete('editHash', 'colourScheme');
            }

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # For current tasks, reset the task (first checking that the task still exists)
            if (
                $self->editObj->taskType eq 'current'
                && $self->session->ivExists('currentTaskHash', $self->editObj->uniqueName)
            ) {
                $self->session->pseudoCmd('resettask ' . $self->editObj->name);
            }

            # For current tasks with a window open, apply the new setting for the colour scheme (if
            #   it was modified)
            if (
                $applyFlag
                && $self->editObj->taskType eq 'current'
                && $self->editObj->winObj
            ) {
                $self->editObj->set_colourScheme($colourScheme);
            }
        }

        return 1;
    }

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid  - The Gtk3::Grid for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Local variables
        my (
            $cmdObj, $packageName, $prettyName,
            @list,
        );

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Names
        $self->addLabel($grid, '<b>Names</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Capitalised name',
            1, 3, 2, 3);
        $self->addEntry($grid, 'prettyName', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Unique name',
            1, 3, 3, 4);
        $self->addEntry($grid, 'uniqueName', FALSE,
            3, 6, 3, 4);

        $self->addLabel($grid, 'Short name',
            7, 9, 1, 2);
        $self->addEntry($grid, 'shortName', FALSE,
            9, 12, 1, 2);
        $self->addLabel($grid, 'Custom task name',
            7, 9, 2, 3);
        if ($self->editObj->taskType && $self->editObj->taskType eq 'custom') {

            $self->addEntry($grid, 'customName', FALSE,
                9, 12, 2, 3);

        } else {

            $self->addLabel($grid, '<i>n/a</i>',
                9, 12, 2, 3);
        }

        # Description and task help
        $self->addLabel($grid, '<b>Description and task help</b>',
            0, 12, 4, 5);
        $self->addLabel($grid, '<i>' . $self->editObj->descrip . '</i>',
            1, 12, 5, 6);

        # Get the ';taskhelp' command object (rather than going through GA::Generic::Cmd)
        $cmdObj = $axmud::CLIENT->ivShow('clientCmdHash', 'taskhelp');
        # Get the package name for the task
        $packageName = $cmdObj->findTaskPackageName($self->session, $self->editObj->name);
        # Remove the 'Games::Axmud::Task::' bit...
        $prettyName = $packageName;
        $prettyName =~ s/^Games\:\:Axmud\:\:Task\:\://;
        # Get the help for the task
        @list = $cmdObj->taskHelp($self->session, $prettyName);

        my $textView = $self->addTextView($grid, undef, FALSE,
            1, 12, 6, 12);
        my $buffer = $textView->get_buffer();
        $buffer->set_text(join("\n", @list));

        # Tab complete
        return 1;
    }

    sub settingsTab {

        # Settings tab
        #
        # 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 . '->settingsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Settings');

        # Add tabs to the inner notebook
        $self->settings1Tab($innerNotebook);
        $self->settings2Tab($innerNotebook);
        $self->settings3Tab($innerNotebook);
        $self->settings4Tab($innerNotebook);
        $self->settings5Tab($innerNotebook);

        return 1;
    }

    sub settings1Tab {

        # Settings1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $editFlag,
            @comboList, @comboList2, @comboList3,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [
                'General settings',
                'Status settings',
                'Start/stop settings',
                'Task loop settings',
                'Text-to-speech settings',
            ],
        );

        # Flag set to FALSE for current tasks, TRUE otherwise
        $editFlag = $self->ivShow('editConfigHash', 'edit_flag');

        # General settings
        $self->addLabel($grid, '<b>General settings</b>',
            0, 6, 0, 1);

        $self->addLabel($grid, 'Category',
            1, 3, 1, 2);
        $self->addEntry($grid, 'category', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Tasklist type',
            1, 3, 2, 3);
        $self->addEntry($grid, 'taskType', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Associated profile',
            1, 3, 3, 4);
        $self->addEntry($grid, 'profName', FALSE,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Profile category',
            1, 3, 4, 5);
        $self->addEntry($grid, 'profCategory', FALSE,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Shortcut IV',
            1, 3, 5, 6);
        $self->addEntry($grid, 'shortCutIV', FALSE,
            3, 6, 5, 6);

        # Status settings
        $self->addLabel($grid, '<b>Status settings</b>',
            0, 6, 6, 7);

        $self->addLabel($grid, 'Status',
            1, 3, 7, 8);
        if ($editFlag) {

            # We use a sub-set of the status values available in GA::Client->constTaskStatusHash
            @comboList = (
                'wait_init',
                'wait_task_exist',
                'wait_task_no_exist',
                'wait_task_start_stop',
            );

            $self->addComboBox($grid, 'status', \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 7, 8);

        } else {

            $self->addEntry($grid, 'status', FALSE,
                3, 6, 7, 8);
        }

        $self->addLabel($grid, 'Resume status',
            1, 3, 8, 9);
        $self->addEntry($grid, 'resumeStatus', FALSE,
            3, 6, 8, 9);
        $self->addLabel($grid, 'Task stage',
            1, 3, 9, 10);
        $self->addEntry($grid, 'stage', FALSE,
            3, 6, 9, 10);

        # Start/stop settings
        $self->addLabel($grid, '<b>Start/stop settings</b>',
            7, 13, 0, 1);
        $self->addLabel($grid, 'Start time',
            8, 11, 1, 2);
        $self->addEntry($grid, 'startTime', $editFlag,
            11, 13, 1, 2);
        $self->addLabel($grid, 'Next check time',
            8, 11, 2, 3);
        $self->addEntry($grid, 'checkTime', $editFlag,
            11, 13, 2, 3);
        $self->addLabel($grid, 'End status',
            8, 11, 3, 4);
        if ($editFlag) {

            # These are all the acceptable values for ->endStatus
            @comboList2 = (
                'unlimited',
                'run_for',
                'run_until',
            );

            $self->addComboBox($grid, 'endStatus', \@comboList2, '',
                TRUE,               # No 'undef' value used
                11, 13, 3, 4);

        } else {

            $self->addEntry($grid, 'endStatus', FALSE,
                11, 13, 3, 4);
        }

        $self->addLabel($grid, 'End time',
            8, 11, 4, 5);
        $self->addEntry($grid, 'endTime', $editFlag,
            11, 13, 4, 5);
        $self->addLabel($grid, 'Dependent task',
            8, 11, 5, 6);
        $self->addEntry($grid, 'waitForTask', $editFlag,
            11, 13, 5, 6);

        # Task loop settings
        $self->addLabel($grid, '<b>Task loop settings</b>',
            7, 12, 6, 7);
        $self->addLabel($grid, 'Call interval (secs)',
            8, 11, 7, 8);
        $self->addEntry($grid, 'delayTime', FALSE,
            11, 13, 7, 8);

        # Text-to-speech (TTS) settings
        $self->addLabel($grid, '<b>Text-to-speech settings</b> <i>(see also pages 3-5)</i>',
            7, 13, 8, 9);
        $self->addLabel($grid, 'TTS configuration used',
            8, 11, 9, 10);

        if ($self->editObj->ttsFlag) {

            # Use an editable combobox if TTS allowed in this task...
            @comboList3 = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('ttsObjHash'));
            my $combo = $self->addComboBox($grid, 'ttsConfig', \@comboList3, '',
                FALSE,               # 'undef' value used
                11, 13, 9, 10);

        } else {

            # ...otherwise, use a normal entry
            $self->addEntry($grid, 'ttsConfig', FALSE,
                11, 13, 9, 10);
        }

        # Tab complete
        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $editFlag,
            @comboList,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            [
                'Flags',
                'Window settings - startup',
                'Window settings - general',
                'Window settings - colour',
            ],
        );

        # Flag set to FALSE for current tasks, TRUE otherwise
        $editFlag = $self->ivShow('editConfigHash', 'edit_flag');

        # Flags
        $self->addLabel($grid, '<b>Flags</b>',
            0, 6, 0, 1);
        $self->addCheckButton($grid, 'Jealous (only 1 copy can run)', 'jealousyFlag', FALSE,
            1, 6, 1, 2);
        $self->addCheckButton($grid, 'Can be used as initial/custom task)', 'storableFlag', FALSE,
            1, 6, 2, 3);
        $self->addCheckButton($grid, 'Requires Locator task', 'requireLocatorFlag', FALSE,
            1, 6, 3, 4);
        $self->addCheckButton($grid, 'Resets when profiles change', 'profSensitivityFlag', FALSE,
            1, 6, 4, 5);
        $self->addCheckButton($grid, 'Active mode on', 'activeFlag', FALSE,
            1, 6, 5, 6);
        $self->addCheckButton($grid, 'Due to shut down (gracefully)', 'shutdownFlag', FALSE,
            1, 6, 6, 7);

        # Window settings - startup
        $self->addLabel($grid, '<b>Window settings - startup</b>',
            0, 6, 7, 8);
        $self->addCheckButton($grid, 'Task window is allowed', 'allowWinFlag', FALSE,
            1, 6, 8, 9);
        $self->addCheckButton($grid, 'Task window is required', 'requireWinFlag', FALSE,
            1, 6, 9, 10);
        my $button = $self->addCheckButton(
            $grid, 'Window opens when task starts', 'startWithWinFlag', FALSE,
            1, 6, 10, 11);
        if ($self->editObj->allowWinFlag && ! $self->editObj->requireWinFlag) {

            $button->set_sensitive(TRUE);
        }

        # Window settings - general
        $self->addLabel($grid, '<b>Window settings - general</b>',
            7, 12, 0, 1);
        $self->addLabel($grid, 'Preferred location(s)',
            8, 10, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            10, 12, 1, 2);
        $entry->set_text(join(', ', $self->editObj->winPreferList));

        $self->addLabel($grid, 'Task window currently open',
            8, 10, 2, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 2, 3);
        if ($self->editObj->winObj) {
            $entry2->set_text('\'grid\' window');
        } elsif ($self->editObj->tableObj) {
            $entry2->set_text('\'main\' window pane');
        } else {
            $entry2->set_text('(not open)');
        }

        $self->addCheckButton($grid, 'Task window uses an entry box', 'taskWinEntryFlag', FALSE,
            8, 12, 3, 4);
        $self->addLabel($grid, 'Winmap used by window',
            8, 10, 4, 5);
        $self->addEntry($grid, 'winmap', FALSE,
            10, 12, 4, 5);
        $self->addLabel($grid, 'Function used to set up the window',
            8, 10, 5, 6);
        $self->addEntry($grid, 'winUpdateFunc', FALSE,
            10, 12, 5, 6);
        $self->addLabel($grid, 'Tab mode',
            8, 10, 6, 7);
        $self->addEntry($grid, 'tabMode', FALSE,
            10, 12, 6, 7);
        $self->addCheckButton($grid, 'Window does not scroll to bottom', 'noScrollFlag', FALSE,
            8, 12, 7, 8);

        # Window settings - colour
        $self->addLabel($grid, '<b>Window settings - colour</b>',
            7, 12, 8, 9);
        $self->addLabel($grid, 'Preferred colour scheme',
            8, 10, 9, 10);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('colourSchemeHash'));
        $self->addComboBox($grid, 'colourScheme', \@comboList, '',
            FALSE,               # 'undef' value used
            10, 12, 9, 10);
        $self->addCheckButton($grid, 'Window is monochrome', 'monochromeFlag', FALSE,
            8, 12, 10, 11);

        # Tab complete
        return 1;
    }

    sub settings3Tab {

        # Settings3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Text-to-speech attributes'],
        );

        # Text-to-speech attributes
        $self->addLabel($grid, '<b>Text-to-speech attributes</b>',
            0, 13, 0, 1);

        $self->addLabel(
            $grid,
            '<i>Attributes used with the \';read\' and \';permread\' commands</i>',
            1, 13, 1, 2);
        # Add a simple list
        @columnList = (
            'Attribute', 'text',
            'Optional value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 13, 2, 10);

        # Initialise the simple list
        $self->settings3Tab_refreshList($slWidget, scalar (@columnList / 2), 'ttsAttribHash');

        # Add editing widgets (but only if this task has some alert attributes)
        if ($self->editObj->ttsAttribHash) {

            $self->addLabel($grid, 'Attribute',
                1, 3, 11, 12);
            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('ttsAttribHash'));
            my $combo = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 5, 11, 12);

            $self->addLabel($grid, 'Optional value',
                5, 7, 11, 12);
            my $entry = $self->addEntry($grid, undef, TRUE,
                7, 9, 11, 12);

            my $button = $self->addButton(
                $grid,
                ';read',
                'Tells the task to read aloud this TTS attribute',
                undef,
                9, 11, 11, 12,
                TRUE,           # Irreversible
            );
            if ($self->editObj->taskType && $self->editObj->taskType ne 'current') {

                # ;read designed for use with current tasks
                $button->set_sensitive(FALSE);
            }
            $button->signal_connect('clicked' => sub {

                my ($attrib, $value, $cmd);

                $attrib = $combo->get_active_text();
                $value = $entry->get_text();

                # (Default attribute value is 'undef', not an empty string)
                if (defined $value && $value ne '') {

                    $self->session->pseudoCmd(
                        'read ' . $attrib . ' ' . $value,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $self->session->pseudoCmd('read ' . $attrib, $self->pseudoCmdMode);
                }

                # Refresh the simple list, but don't reset the entry box
                $self->settings3Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    'ttsAttribHash',
                );
            });

            my $button2 = $self->addButton(
                $grid,
                ';permread',
                'Tells the task to read aloud this TTS attribute',
                undef,
                11, 13, 11, 12,
                TRUE,           # Irreversible
            );
            $button2->signal_connect('clicked' => sub {

                my ($attrib, $value, $cmd);

                $attrib = $combo->get_active_text();
                $value = $entry->get_text();

                # ( Default attribute value is 'undef', not an empty string)
                if (defined $value && $value ne '') {

                    $self->session->pseudoCmd(
                        'permread ' . $attrib . ' ' . $value,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $self->session->pseudoCmd('permread ' . $attrib, $self->pseudoCmdMode);
                }

                # Refresh the simple list, but don't reset the entry box
                $self->settings3Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    'ttsAttribHash',
                );
            });
        }

        # Tab complete
        return 1;
    }

    sub settings3Tab_refreshList {

        # Called by $self->settings3Tab, ->settings4Tab and ->settings5 Tab to refresh the
        #   GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $iv         - 'ttsAttribHash', 'ttsFlagAttribHash' or 'ttsAlertAttribHash'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $iv, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->settings3Tab_refreshList',
                @_,
            );
        }

        # Compile the simple list data
        foreach my $key (sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys($iv))) {

            if ($iv eq 'ttsFlagAttribHash') {

                # 3 columns
                push (@dataList, $key, $self->editObj->ivShow($iv, $key), '');

            } else {

                # 2 columns
                push (@dataList, $key, $self->editObj->ivShow($iv, $key));
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub settings4Tab {

        # Settings4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Text-to-speech flag attributes'],
        );

        # Text-to-speech flag attributes
        $self->addLabel($grid, '<b>Text-to-speech flag attributes</b>',
            0, 13, 0, 1);

        $self->addLabel(
            $grid,
            '<i>Attributes used with the \';switch\' and \';permswitch\' commands</i>',
            1, 13, 1, 2);
        # Add a simple list
        @columnList = (
            'Flag attribute', 'text',
            'Value', 'bool',
            '', 'text',                 # Dummy column to stop checkbox being in middle of window
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 13, 2, 10);

        # Initialise the simple list
        $self->settings3Tab_refreshList($slWidget, scalar (@columnList / 2), 'ttsFlagAttribHash');

        # Add editing widgets (but only if this task has some alert attributes)
        if ($self->editObj->ttsFlagAttribHash) {

            $self->addLabel($grid, 'Flag attribute',
                1, 3, 11, 12);
            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('ttsFlagAttribHash'));
            my $combo = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 5, 11, 12);

            my $button = $self->addButton(
                $grid,
                ';switch',
                'Tells the task to toggle this TTS attribute',
                undef,
                9, 11, 11, 12,
                TRUE,           # Irreversible
            );
            if ($self->editObj->taskType && $self->editObj->taskType ne 'current') {

                # ;switch designed for use with current tasks
                $button->set_sensitive(FALSE);
            }
            $button->signal_connect('clicked' => sub {

                my ($attrib, $cmd);

                $attrib = $combo->get_active_text();

                $self->session->pseudoCmd('switch ' . $attrib, $self->pseudoCmdMode);

                # Refresh the simple list, but don't reset the entry box
                $self->settings3Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    'ttsFlagAttribHash',
                );
            });

            my $button2 = $self->addButton(
                $grid,
                ';permswitch',
                'Tells the task to toggle this TTS attribute',
                undef,
                11, 13, 11, 12,
                TRUE,           # Irreversible
            );
            $button2->signal_connect('clicked' => sub {

                my ($attrib, $cmd);

                $attrib = $combo->get_active_text();
                $self->session->pseudoCmd('permswitch ' . $attrib, $self->pseudoCmdMode);

                # Refresh the simple list, but don't reset the entry box
                $self->settings3Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    'ttsFlagAttribHash',
                );
            });
        }

        # Tab complete
        return 1;
    }

    sub settings5Tab {

        # Settings5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Text-to-speech alert attributes'],
        );

        # Text-to-speech alert attributes
        $self->addLabel($grid, '<b>Text-to-speech alert attributes</b>',
            0, 13, 0, 1);

        $self->addLabel(
            $grid,
            '<i>Attributes used with the \';alert\' and \';permalert\' commands</i>',
            1, 13, 1, 2);
        # Add a simple list
        @columnList = (
            'Alert attribute', 'text',
            'Optional value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 13, 2, 10);

        # Initialise the simple list
        $self->settings3Tab_refreshList(
            $slWidget,
            scalar (@columnList / 2),
            'ttsAlertAttribHash',
        );

        # Add editing widgets (but only if this task has some alert attributes)
        if ($self->editObj->ttsAlertAttribHash) {

            $self->addLabel($grid, 'Alert attribute',
                1, 3, 11, 12);
            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('ttsAlertAttribHash'));
            my $combo = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 5, 11, 12);

            $self->addLabel($grid, 'Optional value',
                5, 7, 11, 12);
            my $entry = $self->addEntry($grid, undef, TRUE,
                7, 9, 11, 12);

            my $button = $self->addButton(
                $grid,
                ';alert',
                'Tells the task to set an alert for this TTS attribute',
                undef,
                9, 11, 11, 12,
                TRUE,           # Irreversible
            );
            if ($self->editObj->taskType && $self->editObj->taskType ne 'current') {

                # ;alert designed for use with current tasks
                $button->set_sensitive(FALSE);
            }
            $button->signal_connect('clicked' => sub {

                my ($attrib, $value, $cmd);

                $attrib = $combo->get_active_text();
                $value = $entry->get_text();

                # (Default attribute value is 'undef', not an empty string)
                if (defined $value && $value ne '') {

                    $self->session->pseudoCmd(
                        'alert ' . $attrib . ' ' . $value,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $self->session->pseudoCmd('alert ' . $attrib, $self->pseudoCmdMode);
                }

                # Refresh the simple list, but don't reset the entry box
                $self->settings3Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    'ttsAlertAttribHash',
                );
            });

            my $button2 = $self->addButton(
                $grid,
                ';permalert',
                'Tells the task to set an alert for this TTS attribute',
                undef,
                11, 13, 11, 12,
                TRUE,           # Irreversible
            );
            $button2->signal_connect('clicked' => sub {

                my ($attrib, $value, $cmd);

                $attrib = $combo->get_active_text();
                $value = $entry->get_text();

                # ( Default attribute value is 'undef', not an empty string)
                if (defined $value && $value ne '') {

                    $self->session->pseudoCmd(
                        'permalert ' . $attrib . ' ' . $value,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $self->session->pseudoCmd('permalert ' . $attrib, $self->pseudoCmdMode);
                }

                # Refresh the simple list, but don't reset the entry box
                $self->settings3Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    'ttsAlertAttribHash',
                );
            });
        }

        # Tab complete
        return 1;
    }

    sub parametersTab {

        # Parameters tab
        #
        # 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 . '->parametersTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Parameters');

        # Add tabs to the inner notebook
        if ($self->editObj->name eq 'attack_task') {

            $self->parametersAttack1Tab($innerNotebook);
            $self->parametersAttack2Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'channels_task') {

            $self->parametersChannels1Tab($innerNotebook);
            $self->parametersChannels2Tab($innerNotebook);
            $self->parametersChannels3Tab($innerNotebook);
            $self->parametersChannels4Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'compass_task') {

            $self->parametersCompass1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'condition_task') {

            $self->parametersCondition1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'connections_task') {

            $self->parametersConnections1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'divert_task') {

            $self->parametersDivert1Tab($innerNotebook);
            $self->parametersDivert2Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'inventory_task') {

            $self->parametersInventory1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'locator_task') {

            $self->parametersLocator1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'raw_token_task') {

            $self->parametersRawToken1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'status_task') {

            $self->parametersStatus1Tab($innerNotebook);
            $self->parametersStatus2Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'system_task') {

            $self->parametersSystem1Tab($innerNotebook);
            $self->parametersSystem2Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'watch_task') {

            $self->parametersWatch1Tab($innerNotebook);

        } else {

            # No user-configurable parameters for these tasks
            $self->parametersDefault1Tab($innerNotebook);
        }

        return 1;
    }

    sub parametersDefault1Tab {

        # Parameters Default1 tab (used by tasks that have no actions)
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersDefault1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [$self->editObj->prettyName . ' task parameters'],
        );

        # Status task actions
        $self->addLabel($grid, '<b>' . $self->editObj->prettyName . ' task parameters</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>This task has no user-configurable parameters</i>',
            1, 12, 1, 2);

        # Tab complete
        return 1;
    }

    sub parametersAttack1Tab {

        # Parameters Attack1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersAttack1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Fight success commands', 'Interaction success commands'],
        );

        # Fight success commands
        $self->addLabel($grid, '<b>Fight success commands</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $grid, '<i>List of commands sent when the character wins a fight (not'
            . ' interpolated)</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'fightCmdList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Interaction success commands
        $self->addLabel($grid, '<b>Interaction success commands</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $grid, '<i>List of commands sent when the character wins an interaction (not'
            . ' interpolated)</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'interactCmdList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub parametersAttack2Tab {

        # Parameters Attack2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersAttack2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Announcements'],
        );

        # Announcements
        $self->addLabel($grid, '<b>Announcements</b>',
            0, 12, 0, 1);

        $self->addCheckButton(
            $grid,
            'Announce kills/completed interactions in \'main\' window',
            'announceFlag',
            TRUE,
            1, 12, 1, 2);

        # Tab complete
        return 1;
    }

    sub parametersChannels1Tab {

        # Parameters Channels1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersChannels1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Summary tab', 'Display options'],
        );

        # Summary tab
        $self->addLabel($grid, '<b>Summary tab</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'single\'', 'summaryMode',
            'single',   # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 1, 2);
        $self->addLabel($grid, 'Use a single tab, in which text for all channels is displayed',
            3, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'multi\'', 'summaryMode', 'multi', TRUE,
            1, 3, 2, 3);
        $self->addLabel($grid,
            'Every channel has its own tab, and in addition a \'summary\' tab shows text in every'
            . ' channel',
            3, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'default\'', 'summaryMode', 'default', TRUE,
            1, 3, 3, 4);
        $self->addLabel($grid,
            'Every channel has its own tab and there is no \'summary\' tab',
            3, 12, 3, 4);

        $self->addLabel($grid,
            'Name of the \'summary\' tab, when used (1-16 characters, including spaces)',
            1, 8, 4, 5);
        $self->addEntryWithIcon($grid, 'summaryChannel', 'string', 1, 16,
            8, 12, 4, 5,
            16, 16);

        # Display options
        $self->addLabel($grid, '<b>Display options</b>',
            0, 12, 5, 6);

        my $checkButton = $self->addCheckButton(
            $grid, 'Tabs in the task window have a close button', 'tabCloseButtonFlag', TRUE,
            1, 12, 6, 7);
        my $checkButton2 = $self->addCheckButton(
            $grid, 'Channels names should be capitalised', 'capitaliseFlag', TRUE,
            1, 12, 7, 8);
        my $checkButton3 = $self->addCheckButton(
            $grid,
            'The line\'s original colour/style tags are preserved in the task window',
            'useColourStyleFlag',
            TRUE,
            1, 12, 8, 9);

        $self->addLabel($grid,
            '<i>NB Changes to the values on this page are applied when task is restarted/reset</i>',
            1, 12, 9, 10);

        # Tab complete
        return 1;
    }

    sub parametersChannels2Tab {

        # Parameters Channels2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersChannels2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Initial channel list', 'Ignore channel list'],
        );

        # Initial channel list
        $self->addLabel($grid, '<b>Initial channel list</b>',
            0, 12, 0, 2);
        $self->addLabel($grid,
            '<i>Lists of channels for which a tab is automatically created when the task window'
            . ' opens</i>',
            1, 12, 2, 4);
        $self->addTextView($grid, 'initChannelList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Ignore channel list
        $self->addLabel($grid, '<b>Ignore channel list</b>',
            0, 12, 6, 8);
        $self->addLabel($grid,
            '<i>Lists of channels which should be ignored (no tab is created and nothing is'
            . ' displayed in the \'summary\' tab)</i>',
            1, 12, 8, 10);
        $self->addTextView($grid, 'ignoreChannelList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub parametersChannels3Tab {

        # Parameters Channels3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersChannels3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Sound effects'],
        );

        # Sound effects
        $self->addLabel($grid, '<b>Sound effects</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of sound effects that should be played when text is redirected into a'
            . ' channel</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Channel', 'text',
            'Sound effect', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'soundEffectHash');

        # Add editing widgets
        $self->addLabel($grid, 'Channel',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            3, 6, 10, 11,
            16, 16);

        $self->addLabel($grid, 'Sound effect (leave empty to play nothing)',
            6, 9, 10, 11);
        my $entry2 = $self->addEntry($grid, undef, TRUE,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'soundEffectHash',
            11,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($channel, $effect);

            $channel = $entry->get_text();
            $effect = $entry2->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('soundEffectHash', $channel, $effect);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2), 'soundEffectHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub parametersChannels4Tab {

        # Parameters Channels4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersChannels4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Urgency hints'],
        );

        # Urgency hints
        $self->addLabel($grid, '<b>Urgency hints</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of channels for which the task window\'s urgency hint should be set (to make'
            . ' the window flash)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Channel', 'text',
            'Urgency hint', 'bool',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'urgencyHash');

        # Add editing widgets
        $self->addLabel($grid, 'Channel',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            3, 6, 10, 11,
            16, 16);

        my $checkButton = $self->addCheckButton(
            $grid, 'Use urgency hint when text redirected', undef, TRUE,
            6, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'urgencyHash',
            11,
            $entry,
        );
        $button->signal_connect('clicked' => sub {

            my ($channel, $flag);

            $channel = $entry->get_text();
            $flag = $checkButton->get_active();

            if ($self->checkEntryIcon($entry)) {

                # Prefer a FALSE/TRUE value
                if (! $flag) {
                    $flag = FALSE;
                } else {
                    $flag = TRUE;
                }

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('urgencyHash', $channel, $flag);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2), 'urgencyHash',
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub parametersCompass1Tab {

        # Parameters Compass1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersCompass1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Keypad commands'],
        );

        # Keypad commands
        $self->addLabel($grid, '<b>Keypad commands</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of customisable keypad commands</i>',
            1, 6, 1, 2);

        my $checkButton = $self->addCheckButton(
            $grid, 'Compass task is (or starts) enabled', 'enabledFlag', FALSE,
            7, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Command', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 10);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'keypadCmdHash');

        # Add editing buttons. For initial tasks, the user can change the commands here; for a task
        #   in the current tasklist, they can't
        if (defined $self->editObj->taskType && $self->editObj->taskType ne 'current') {

            $self->addLabel($grid, 'Key:',
                1, 3, 10, 11);

            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('keypadCmdHash'));
            my $combo = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,              # no 'undef' value allowed
                3, 6, 10, 11);

            $self->addLabel($grid, 'Command:',
                6, 8, 10, 11);
            my $entry = $self->addEntry($grid, undef, TRUE,
                8, 12, 10, 11);

            my $button = $self->addButton(
                $grid, 'Set command', 'Set the command for this key', undef,
                8, 10, 11, 12);
            $button->signal_connect('clicked' => sub {

                my ($key, $cmd);

                $key = $combo->get_active_text();
                $cmd = $entry->get_text();
                if (defined $cmd && $cmd eq '') {

                    # Prefer 'undef' rather than empty string
                    $cmd = undef;
                }

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('keypadCmdHash', $key, $cmd);

                # Refresh the simple list and reset the entry
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'keypadCmdHash');
                $self->resetEntryBoxes($entry);
            });
        }

        my $button2;

        if (defined $self->editObj->taskType && $self->editObj->taskType ne 'current') {

            $button2 = $self->addButton(
                $grid, 'Reset list', 'Reset the list of keypad world commands', undef,
                10, 12, 11, 12);

        } else {

            $button2 = $self->addButton(
                $grid, 'Refresh list', 'Refresh the list of keypad world commands', undef,
                10, 12, 10, 11);
        }

        $button2->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'keypadCmdHash');

            # Update the checkbutton
            $checkButton->set_active($self->editObj->enabledFlag);

            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'keypadCmdHash');
        });

        # Tab complete
        return 1;
    }

    sub parametersCondition1Tab {

        # Parameters Condition1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->parametersCondition1Tab',
                @_,
            );
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Object sensitivity', 'Check parameters'],
        );

        # Object sensitivity
        $self->addLabel($grid, '<b>Object sensitivity</b>',
            0, 12, 0, 1);

        $self->addLabel(
            $grid, 'How closely model objects much match, when compared (0-100)',
            1, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'sensitivity', 'int', 0, 100,
            9, 12, 1, 2);

        $self->addLabel(
            $grid, '<i>0 - any objects match, 100 - only identical objects match,</i>',
            2, 12, 2, 3);
        $self->addLabel(
            $grid, '<i>70 - objects must be fairly similar to match</i>',
            2, 12, 3, 4);
        $self->addLabel(
            $grid, '<i>90 - objects must be very similar to match</i>',
            2, 12, 4, 5);

        # Check parameters
        $self->addLabel($grid, '<b>Check parameters</b>',
            0, 12, 5, 6);

        my $checkButton = $self->addCheckButton(
            $grid, 'Condition check performed only once', undef, TRUE,
            1, 12, 6, 7);

        $self->addLabel($grid, 'Seconds to wait before first check',
            1, 9, 7, 8);
        my $entry = $self->addEntryWithIcon($grid, 'firstCheckTime', 'int', 0, undef,
            9, 12, 7, 8);
        $self->addLabel($grid, 'Seconds to wait between checks',
            1, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, 'waitTime', 'int', 1, undef,
            9, 12, 8, 9);
        $self->addLabel($grid, 'Max seconds to wait while checking each object',
            1, 9, 9, 10);
        my $entry3 = $self->addEntryWithIcon($grid, 'maxObjTime', 'int', 1, undef,
            9, 12, 9, 10);

        # The three entries must be insensitive, when the checkbutton is toggled
        $checkButton->signal_connect('toggled' => sub {

            $self->ivAdd('editHash', 'checkOnceFlag', $checkButton->get_active());

            if ($checkButton->get_active()) {
                $self->sensitiseWidgets($entry, $entry2, $entry3);
            } else {
                $self->desensitiseWidgets($entry, $entry2, $entry3);
            }
        });
        $checkButton->set_active($self->editObj->checkOnceFlag);

        # Tab complete
        return 1;
    }

    sub parametersConnections1Tab {

        # Parameters Connections1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            @list, @comboList,
            %descripHash,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->parametersConnections1Tab',
                @_,
            );
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Task parameters'],
        );

        # Task parameters
        $self->addLabel($grid, '<b>Task parameters</b>',
            0, 12, 0, 1);

        $self->addCheckButton(
            $grid,
            'Show additional connection information (requires a larger task window)',
            'showInfoFlag',
            TRUE,
            1, 12, 1, 2);
        $self->addCheckButton(
            $grid,
            'This task create its own macros to switch between sessions',
            'useMacrosFlag',
            TRUE,
            1, 12, 2, 3);

        $self->addLabel($grid,'If macros are created, which keycodes to use',
            1, 6, 3, 4);

        @list = (
            'CTRL+1, CTRL+2 ... CTRL+9'     => 'default',
            'F1, F2 ... F9'                 => 'simple',
        );

        do {

            my ($descrip, $mode);

            $descrip = shift @list;
            $mode = shift @list;

            $descripHash{$descrip} = $mode;

            if ($self->editObj->macroMode eq $mode) {
                unshift(@comboList, $descrip);
            } else {
                push (@comboList, $descrip);
            }

        } until (! @list);

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # 'undef' value not allowed
            6, 12, 3, 4);
        $comboBox->signal_connect('changed' => sub {

            $self->ivAdd('editHash', 'macroMode', $descripHash{$comboBox->get_active_text()});
        });

        # Tab complete
        return 1;
    }

    sub parametersDivert1Tab {

        # Parameters Divert1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@comboList, @comboList2);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersDivert1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Background colour parameters (1/2)'],
        );

        # Import a list of standard colour tags, putting bold colours first
        @comboList = ($axmud::CLIENT->constBoldColourTagList, $axmud::CLIENT->constColourTagList);
        # Import a sorted list of sound effects
        @comboList2 = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('customSoundHash'));

        # Background colour parameters (1/2)
        $self->addLabel($grid, '<b>Background colour parameters (1/2)</b>',
            0, 13, 0, 1);

        $self->addLabel($grid, 'Default colour',
            1, 3, 1, 2);
        $self->addComboBox($grid, 'defaultColour', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 5, 1, 2);
        $self->addLabel($grid, '<i>(Leave empty to use the normal background colour)</i>',
            5, 12, 1, 2);

        $self->addLabel($grid, '<u>\'tell\'</u> channel colour',
            1, 3, 2, 3);
        $self->addComboBox($grid, 'tellAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 2, 3);
        $self->addLabel($grid, 'Interval (seconds)',
            5, 7, 2, 3);
        $self->addEntryWithIcon($grid, 'tellAlertInterval', 'int', 1, undef,
            7, 9, 2, 3,
            8, 8);      # Max width
        $self->addLabel($grid, 'Sound',
            9, 11, 2, 3);
        $self->addComboBox($grid, 'tellAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 2, 3);

        $self->addLabel($grid, 'Char limit <i>(0 - whole line)</i>',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'tellCharLimit', 'int', 0, undef,
            3, 5, 3, 4,
            8, 8);              # Max width
        $self->addCheckButton($grid, 'Show room number', 'tellRoomFlag', TRUE,
            5, 9, 3, 4);
        $self->addCheckButton($grid, 'Show urgency hint', 'tellUrgencyFlag', TRUE,
            9, 13, 3, 4);

        $self->addLabel($grid, '<u>\'social\' channel colour</u>',
            1, 3, 4, 5);
        $self->addComboBox($grid, 'socialAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 4, 5);
        $self->addLabel($grid, 'Interval (seconds)',
            5, 7, 4, 5);
        $self->addEntryWithIcon($grid, 'socialAlertInterval', 'int', 1, undef,
            7, 9, 4, 5,
            8, 8);              # Max width
        $self->addLabel($grid, 'Sound',
            9, 11, 4, 5);
        $self->addComboBox($grid, 'socialAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 4, 5);

        $self->addLabel($grid, 'Character limit',
            1, 3, 5, 6);
        $self->addEntryWithIcon($grid, 'socialCharLimit', 'int', 0, undef,
            3, 5, 5, 6,
            8, 8);              # Max width
        $self->addCheckButton($grid, 'Show room number', 'socialRoomFlag', TRUE,
            5, 9, 5, 6);
        $self->addCheckButton($grid, 'Show urgency hint', 'socialUrgencyFlag', TRUE,
            9, 13, 5, 6);

        $self->addLabel($grid, '<u>\'custom\' channel colour</u>',
            1, 3, 6, 7);
        $self->addComboBox($grid, 'customAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 6, 7);
        $self->addLabel($grid, 'Interval (seconds)',
            5, 7, 6, 7);
        $self->addEntryWithIcon($grid, 'customAlertInterval', 'int', 1, undef,
            7, 9, 6, 7,
            8, 8);              # Max width
        $self->addLabel($grid, 'Sound',
            9, 11, 6, 7);
        $self->addComboBox($grid, 'customAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 6, 7);

        $self->addLabel($grid, 'Character limit',
            1, 3, 7, 8);
        $self->addEntryWithIcon($grid, 'customCharLimit', 'int', 0, undef,
            3, 5, 7, 8,
            8, 8);              # Max width
        $self->addCheckButton($grid, 'Show room number', 'customRoomFlag', TRUE,
            5, 9, 7, 8);
        $self->addCheckButton($grid, 'Show urgency hint', 'customUrgencyFlag', TRUE,
            9, 13, 7, 8);

        $self->addLabel($grid, '<u>\'warning\' channel colour</u>',
            1, 3, 8, 9);
        $self->addComboBox($grid, 'warningAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 8, 9);
        $self->addLabel($grid, 'Interval (seconds)',
            5, 7, 8, 9);
        $self->addEntryWithIcon($grid, 'warningAlertInterval', 'int', 1, undef,
            7, 9, 8, 9,
            8, 8);              # Max width
        $self->addLabel($grid, 'Sound',
            9, 11, 8, 9);
        $self->addComboBox($grid, 'warningAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 8, 9);

        $self->addLabel($grid, 'Character limit',
            1, 3, 9, 10);
        $self->addEntryWithIcon($grid, 'warningCharLimit', 'int', 0, undef,
            3, 5, 9, 10,
            8, 8);              # Max width
        $self->addCheckButton($grid, 'Show room number', 'warningRoomFlag', TRUE,
            5, 9, 9, 10);
        $self->addCheckButton($grid, 'Show urgency hint', 'warningUrgencyFlag', TRUE,
            9, 13, 9, 10);

        # Tab complete
        return 1;
    }

    sub parametersDivert2Tab {

        # Parameters Divert2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@comboList, @comboList2);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersDivert2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Background colour parameters (2/2)'],
        );

        # Import a list of standard colour tags, putting bold colours first
        @comboList = ($axmud::CLIENT->constBoldColourTagList, $axmud::CLIENT->constColourTagList);
        # Import a sorted list of sound effects
        @comboList2 = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('customSoundHash'));

        # Background colour parameters (2/2)
        $self->addLabel($grid, '<b>Background colour parameters (2/2)</b>',
            0, 13, 0, 1);

        $self->addLabel($grid, '<u>All other channels</u>',
            1, 3, 1, 2);
        $self->addComboBox($grid, 'otherAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 1, 2);
        $self->addLabel($grid, 'Interval (seconds)',
            5, 7, 1, 2);
        $self->addEntryWithIcon($grid, 'otherAlertInterval', 'int', 1, undef,
            7, 9, 1, 2,
            8, 8);              # Max width
        $self->addLabel($grid, 'Sound',
            9, 11, 1, 2);
        $self->addComboBox($grid, 'otherAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 1, 2);

        $self->addLabel($grid, 'Character limit',
            1, 3, 2, 3);
        $self->addEntryWithIcon($grid, 'otherCharLimit', 'int', 0, undef,
            3, 5, 2, 3,
            8, 8);              # Max width
        $self->addCheckButton($grid, 'Show room number', 'otherRoomFlag', TRUE,
            5, 9, 2, 3);
        $self->addCheckButton($grid, 'Show urgency hint', 'otherUrgencyFlag', TRUE,
            9, 13, 2, 3);

        # Tab complete
        return 1;
    }

    sub parametersInventory1Tab {

        # Parameters Inventory1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->parametersInventory1Tab',
                @_,
            );
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Object sensitivity'],
        );

        # Object sensitivity
        $self->addLabel($grid, '<b>Object sensitivity</b>',
            0, 12, 0, 1);

        $self->addLabel(
            $grid, 'How closely model objects much match, when compared (0-100)',
            1, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'sensitivity', 'int', 0, 100,
            9, 12, 1, 2);

        $self->addLabel(
            $grid, '<i>0 - any objects match, 100 - only identical objects match,</i>',
            2, 12, 2, 3);
        $self->addLabel(
            $grid, '<i>70 - objects must be fairly similar to match</i>',
            2, 12, 3, 4);
        $self->addLabel(
            $grid, '<i>90 - objects must be very similar to match</i>',
            2, 12, 4, 5);

        # Tab complete
        return 1;
    }

    sub parametersLocator1Tab {

        # Parameters Locator1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersLocator1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Task window', 'Manual resets'],
        );

        # Task window
        $self->addLabel($grid, '<b>Task window</b>',
            0, 12, 0, 1);

        $self->addLabel(
            $grid, 'Description max characters (leave empty to display whole description)',
            1, 9, 1, 2);
        $self->addEntryWithIcon($grid, 'winDescripLimit', 'int', 0, undef,
            9, 12, 1, 2,
            8, 8);

        $self->addCheckButton(
            $grid, 'Show multiple corpses on single line', 'combineCorpseFlag', TRUE,
            1, 12, 2, 3);
        $self->addCheckButton(
            $grid, 'Show multiple body parts on single line', 'combineBodyPartFlag', TRUE,
            1, 12, 3, 4);
        $self->addCheckButton(
            $grid, 'When listing objects, show nouns first', 'showParsedFlag', TRUE,
            1, 12, 4, 5);

        # Manual resets
        $self->addLabel($grid, '<b>Manual resets</b>',
            0, 12, 5, 6);

        my ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'do_nothing\'', 'autoLookMode',
            'do_nothing',   # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 3, 6, 7);
        $self->addLabel(
            $grid,
            'Look for room statements only in lines received after the manual reset',
            3, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'search_back\'', 'autoLookMode', 'search_back', TRUE,
            1, 3, 7, 8);
        $self->addLabel(
            $grid,
            'After a manual reset, look for most recently-received room statement',
            3, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'send_look\'', 'autoLookMode', 'send_look', TRUE,
            1, 3, 8, 9);
        $self->addLabel(
            $grid,
            'After a manual reset, send a \'look\' command and use the response',
            3, 12, 8, 9);

        # Tab complete
        return 1;
    }

    sub parametersRawToken1Tab {

        # Parameters RawToken1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersRawToken1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Task window'],
        );

        # Task window
        $self->addLabel($grid, '<b>Task window</b>',
            0, 12, 0, 1);

        # Left column
        $self->addCheckButton($grid, 'Split lines, with one line per token', 'splitLineFlag', TRUE,
            1, 6, 1, 2);
        $self->addCheckButton($grid, 'Show token types', 'showTypeFlag', TRUE,
            1, 6, 2, 3);

        # Right column
        $self->addCheckButton($grid, 'Show packet numbers', 'countPacketFlag', TRUE,
            7, 12, 1, 2);

        # Tab complete
        return 1;
    }

    sub parametersStatus1Tab {

        # Parameters Status1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersStatus1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Background colour parameters', 'Data display'],
        );

        # (Import a list of standard colour tags, putting bold colours first)
        @comboList = ($axmud::CLIENT->constColourTagList, $axmud::CLIENT->constBoldColourTagList);

        # Background colour parameters
        $self->addLabel($grid, '<b>Background colour parameters</b>',
            0, 12, 0, 1);

        $self->addCheckButton($grid, 'Allow background colour changes', 'allowColourFlag', TRUE,
            1, 6, 1, 2);
        $self->addLabel($grid, 'Character alive',
            1, 3, 2, 3);
        $self->addComboBox($grid, 'aliveColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 2, 3);
        $self->addLabel($grid, 'Alive, health below 50%',
            1, 3, 3, 4);
        $self->addComboBox($grid, 'alive50Colour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 3, 4);
        $self->addLabel($grid, 'Alive, health below 30%',
            1, 3, 4, 5);
        $self->addComboBox($grid, 'alive30Colour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 4, 5);
        $self->addLabel($grid, 'Alive, health below 10%',
            1, 3, 5, 6);
        $self->addComboBox($grid, 'alive10Colour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 5, 6);

        # (right column)
        $self->addLabel($grid, 'Character asleep',
            7, 9, 2, 3);
        $self->addComboBox($grid, 'asleepColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 2, 3);
        $self->addLabel($grid, 'Character passed out',
            7, 9, 3, 4);
        $self->addComboBox($grid, 'passedOutColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 3, 4);
        $self->addLabel($grid, 'Character dead',
            7, 9, 4, 5);
        $self->addComboBox($grid, 'deadColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 4, 5);

        # Data display
        $self->addLabel($grid, '<b>Data display</b>',
            0, 12, 6, 7);
        $self->addCheckButton(
            $grid, 'Show information in \'main\' window gauges', 'gaugeFlag', TRUE,
            1, 6, 7, 8);
        $self->addCheckButton($grid, 'Also show explicit gauge labels', 'gaugeValueFlag', TRUE,
            1, 6, 8, 9);

        # Tab complete
        return 1;
    }

    sub parametersStatus2Tab {

        # Parameters Status2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersStatus2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Status task commands'],
        );

        # Status task commands
        $self->addLabel($grid, '<b>Status task commands</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>List of world commands sent by the task periodically</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Command', 'text',
            'Interval (secs)', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'cmdHash');

        # Add entries/comboboxes for adding new commands
        $self->addLabel($grid, 'Command',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($grid, 'Interval',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'cmdHash',
            9,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($cmd, $value);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $cmd = $entry->get_text();
                $value = $entry2->get_text();

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('cmdHash', $cmd, $value);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'cmdHash');
                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub parametersSystem1Tab {

        # Parameters System1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersSystem1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['System message display modes'],
        );

        # System message display modes
        $self->addLabel($grid, '<b>System message display modes</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<i>Ordinary system messages</i>',
            1, 6, 1, 2);

        my ($group, $group2, $group3, $group4, $group5, $radioButton);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'main\'', 'systemMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 2, 3);
        $self->addLabel($grid, 'Displayed in the \'main\' window only',
            3, 6, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'both\'', 'systemMode', 'both', TRUE,
            1, 3, 3, 4);
        $self->addLabel($grid, 'Displayed in \'main\' and task windows',
            3, 6, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $grid, $group, '\'task\'', 'systemMode', 'task', TRUE,
            1, 3, 4, 5);
        $self->addLabel($grid, 'Displayed in the task window only',
            3, 6, 4, 5);

        $self->addLabel($grid, '<i>Error messages</i>',
            1, 6, 5, 6);

        ($group2, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'main\'', 'errorMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 6, 7);
        $self->addLabel($grid, 'Displayed in the \'main\' window only',
            3, 6, 6, 7);

        ($group2, $radioButton) = $self->addRadioButton(
            $grid, $group2, '\'both\'', 'errorMode', 'both', TRUE,
            1, 3, 7, 8);
        $self->addLabel($grid, 'Displayed in \'main\' and task windows',
            3, 6, 7, 8);

        ($group2, $radioButton) = $self->addRadioButton(
            $grid, $group2, '\'task\'', 'errorMode', 'task', TRUE,
            1, 3, 8, 9);
        $self->addLabel($grid, 'Displayed in the task window only',
            3, 6, 8, 9);

        $self->addLabel($grid, '<i>Warning messages</i>',
            1, 6, 9, 10);

        ($group3, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'main\'', 'warningMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 10, 11);
        $self->addLabel($grid, 'Displayed in the \'main\' window only',
            3, 6, 10, 11);

        ($group3, $radioButton) = $self->addRadioButton(
            $grid, $group3, '\'both\'', 'warningMode', 'both', TRUE,
            1, 3, 11, 12);
        $self->addLabel($grid, 'Displayed in \'main\' and task windows',
            3, 6, 11, 12);

        ($group3, $radioButton) = $self->addRadioButton(
            $grid, $group3, '\'task\'', 'warningMode', 'task', TRUE,
            1, 3, 12, 13);
        $self->addLabel($grid, 'Displayed in the task window only',
            3, 6, 12, 13);

        # Right column
        $self->addLabel($grid, '<i>Debug messages</i>',
            7, 12, 5, 6);

        ($group4, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'main\'', 'debugMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            7, 9, 6, 7);
        $self->addLabel($grid, 'Displayed in the \'main\' window only',
            9, 12, 6, 7);

        ($group4, $radioButton) = $self->addRadioButton(
            $grid, $group4, '\'both\'', 'debugMode', 'both', TRUE,
            7, 9, 7, 8);
        $self->addLabel($grid, 'Displayed in \'main\' and task windows',
            9, 12, 7, 8);

        ($group4, $radioButton) = $self->addRadioButton(
            $grid, $group4, '\'task\'', 'debugMode', 'task', TRUE,
            7, 9, 8, 9);
        $self->addLabel($grid, 'Displayed in the task window only',
            9, 12, 8, 9);

        $self->addLabel($grid, '<i>Improper arguments messages</i>',
            7, 12, 9, 10);

        ($group5, $radioButton) = $self->addRadioButton(
            $grid, undef, '\'main\'', 'improperMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            7, 9, 10, 11);
        $self->addLabel($grid, 'Displayed in the \'main\' window only',
            9, 12, 10, 11);

        ($group5, $radioButton) = $self->addRadioButton(
            $grid, $group5, '\'both\'', 'improperMode', 'both', TRUE,
            7, 9, 11, 12);
        $self->addLabel($grid, 'Displayed in \'main\' and task windows',
            9, 12, 11, 12);

        ($group5, $radioButton) = $self->addRadioButton(
            $grid, $group5, '\'task\'', 'improperMode', 'task', TRUE,
            7, 9, 12, 13);
        $self->addLabel($grid, 'Displayed in the task window only',
            9, 12, 12, 13);

        # Tab complete
        return 1;
    }

    sub parametersSystem2Tab {

        # Parameters System2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersSystem2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['System message colours'],
        );

        # System message colours
        $self->addLabel($grid, '<b>System message colours</b>',
            0, 12, 0, 1);

        $self->addCheckButton($grid, 'Preserve colour in task window', 'colourFlag', TRUE,
            1, 12, 1, 2);

        # Tab complete
        return 1;
    }

    sub parametersWatch1Tab {

        # Parameters Watch1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->parametersWatch1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Background colour parameters'],
        );

        # (Import a list of standard colour tags, putting bold colours first)
        @comboList = ($axmud::CLIENT->constBoldColourTagList, $axmud::CLIENT->constColourTagList);

        # Background colour parameters
        $self->addLabel($grid, '<b>Background colour parameters</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Default background colour',
            1, 3, 2, 3);
        $self->addComboBox($grid, 'defaultColour', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);
        $self->addLabel($grid, '<i>(Leave empty to use the normal background colour)</i>',
            6, 12, 2, 3);

        $self->addLabel($grid, 'Channels task alert colour',
            1, 3, 3, 4);
        $self->addComboBox($grid, 'channelsAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 3, 4);
        $self->addLabel($grid, 'Interval (seconds)',
            7, 9, 3, 4);
        $self->addEntryWithIcon($grid, 'channelsAlertInterval', 'int', 1, undef,
            9, 12, 3, 4);

        $self->addLabel($grid, 'Chat task alert colour',
            1, 3, 4, 5);
        $self->addComboBox($grid, 'chatAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 4, 5);
        $self->addLabel($grid, 'Interval (seconds)',
            7, 9, 4, 5);
        $self->addEntryWithIcon($grid, 'chatAlertInterval', 'int', 1, undef,
            9, 12, 4, 5);

        $self->addLabel($grid, 'Divert task alert colour',
            1, 3, 5, 6);
        $self->addComboBox($grid, 'divertAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 5, 6);
        $self->addLabel($grid, 'Interval (seconds)',
            7, 9, 5, 6);
        $self->addEntryWithIcon($grid, 'divertAlertInterval', 'int', 1, undef,
            9, 12, 5, 6);

        # Tab complete
        return 1;
    }

    sub actionsTab {

        # Actions tab
        #
        # 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 . '->actionsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Actions');

        # Add tabs to the inner notebook
        if ($self->editObj->name eq 'channels_task') {

            $self->actionsChannels1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'compass_task') {

            $self->actionsCompass1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'divert_task') {

            $self->actionsDivert1Tab($innerNotebook);

        } elsif (
            $self->editObj->name eq 'inventory_task'
            || $self->editObj->name eq 'condition_task'
        ) {
            $self->actionsInventory1Tab($innerNotebook);
            $self->actionsInventory2Tab($innerNotebook);
            $self->objects1Tab($innerNotebook);     # Inherited from GA::Generic::EditWin
            $self->objects2Tab($innerNotebook);     # Inherited from GA::Generic::EditWin

        } elsif ($self->editObj->name eq 'locator_task') {

            $self->actionsLocator1Tab($innerNotebook);
            $self->actionsLocator2Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'status_task') {

            $self->actionsStatus1Tab($innerNotebook);

        } elsif ($self->editObj->name eq 'watch_task') {

            $self->actionsWatch1Tab($innerNotebook);

        } else {

            # No actions for these tasks
            $self->actionsDefault1Tab($innerNotebook);
        }

        return 1;
    }

    sub actionsDefault1Tab {

        # Actions Default1 tab (used by tasks that have no actions)
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsDefault1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [$self->editObj->prettyName . ' task actions'],
        );

        # Status task actions
        $self->addLabel($grid, '<b>' . $self->editObj->prettyName . ' task actions</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>This task has no actions</i>',
            1, 12, 1, 2);

        # Tab complete
        return 1;
    }

    sub actionsChannels1Tab {

        # Actions Channels1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsChannels1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Channels task actions'],
        );

        # Channels task actions
        $self->addLabel($grid, '<b>Channels task actions</b>',
            0, 12, 0, 1);

        my $button = $self->addButton($grid, 'Empy window', '', undef,
            1, 5, 1, 2,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptychannelswindow', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Empty the task window',
            6, 12, 1, 2);

        my $button2 = $self->addButton($grid, 'Edit patterns...', '', undef,
            1, 5, 2, 3);
        $button2->signal_connect('clicked' => sub {

            # Open a new 'edit' window, with this window as the parent
            $self->createFreeWin(
                'Games::Axmud::EditWin::Profile::World',
                $self,
                $self->session,
                'Edit world profile \'' . $self->session->currentWorld->name . '\'',
                $self->session->currentWorld,
                FALSE,              # Not temporary
            );
        });
        $self->addLabel($grid, 'Edit channel patterns in the current world profile',
            6, 12, 2, 3);

        # Tab complete
        return 1;
    }

    sub actionsCompass1Tab {

        # Actions Compass1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsCompass1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Compass task actions'],
        );

        # Compass task actions
        $self->addLabel($grid, '<b>Compass task actions</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, '<i>Current Compass task</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($grid, 'Enable task', '', undef,
            2, 6, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('compass on', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Sends world commands when each keypad key is pressed',
            7, 12, 2, 3);

        my $button2 = $self->addButton($grid, 'Disable task', '', undef,
            2, 6, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('compass off', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Keypad keys behave normally',
            7, 12, 3, 4);

        $self->addLabel($grid, '<i>Current and/or initial Compasss task</i>',
            1, 12, 4, 5);

        my $button3 = $self->addButton($grid, 'Enable task(s)', '', undef,
            2, 6, 5, 6,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('permcompass on', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Sends world commands when each keypad key is pressed',
            7, 12, 5, 6);

        my $button4 = $self->addButton($grid, 'Disable task(s)', '', undef,
            2, 6, 6, 7,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('permcompass off', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Keypad keys behave normally',
            7, 12, 6, 7);

        $self->addLabel($grid, '<i>Customised world commands</i>',
            1, 12, 7, 8);

        @comboList = (
            'kp_0',
            'kp_5',
            'kp_divide',
            'kp_multiply',
            'kp_full_stop',
            'kp_enter',
        );

        $self->addLabel($grid, 'Keypad key',
            2, 4, 8, 9);
        my $combo = $self->addComboBox($grid, undef, \@comboList, 'Select key:',
            TRUE,               # No 'undef' value used
            4, 6, 8, 9);
        $self->addLabel($grid, 'World command',
            7, 8, 8, 9);
        my $entry = $self->addEntry($grid, undef, TRUE,
            8, 12, 8, 9);
        my $button5 = $self->addButton($grid, 'Set for current', '', undef,
            8, 10, 9, 10,
            TRUE,           # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            my ($keycode, $cmd);

            $keycode = $combo->get_active_text();
            $cmd = $entry->get_text();

            if ($keycode) {

                if ($cmd) {

                    $self->session->pseudoCmd(
                        'compass ' . $keycode . ' ' . $cmd,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $self->session->pseudoCmd('compass ' . $keycode, $self->pseudoCmdMode);
                }
            }
        });
        my $button6 = $self->addButton($grid, 'Set for current/initial', '', undef,
            10, 12, 9, 10,
            TRUE,           # Irreversible
        );
        $button6->signal_connect('clicked' => sub {

            my ($keycode, $cmd);

            $keycode = $combo->get_active_text();
            $cmd = $entry->get_text();

            if ($keycode) {

                if ($cmd) {

                    $self->session->pseudoCmd(
                        'permcompass ' . $keycode . ' ' . $cmd,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $self->session->pseudoCmd('permcompass ' . $keycode, $self->pseudoCmdMode);
                }
            }
        });

        # Tab complete
        return 1;
    }

    sub actionsDivert1Tab {

        # Actions Divert1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsDivert1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Divert task actions'],
        );

        # Divert task actions
        $self->addLabel($grid, '<b>Divert task actions</b>',
            0, 12, 0, 1);

        my $button = $self->addButton($grid, 'Empy window', '', undef,
            1, 5, 1, 2,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptydivertwindow', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Empty the task window',
            6, 12, 1, 2);

        my $button2 = $self->addButton($grid, 'Edit patterns...', '', undef,
            1, 5, 2, 3);
        $button2->signal_connect('clicked' => sub {

            # Open a new 'edit' window, with this window as the parent
            $self->createFreeWin(
                'Games::Axmud::EditWin::Profile::World',
                $self,
                $self->session,
                'Edit world profile \'' . $self->session->currentWorld->name . '\'',
                $self->session->currentWorld,
                FALSE,              # Not temporary
            );
        });
        $self->addLabel($grid, 'Edit channel patterns in the current world profile',
            6, 12, 2, 3);

        # Tab complete
        return 1;
    }

    sub actionsInventory1Tab {

        # Actions Inventory1 tab (also used with Condition task)
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsInventory1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Inventory/Condition task actions'],
        );

        # Inventory/Condition task actions
        $self->addLabel($grid, '<b>Inventory/Condition task actions</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, '<i>Active mode</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($grid, 'Activate tasks', '', undef,
            2, 4, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('activateinventory', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Put the Inventory and/or Condition tasks in \'active\' mode',
            4, 12, 2, 3);

        my $button2 = $self->addButton($grid, 'Disactivate tasks', '', undef,
            2, 4, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('disactivateinventory', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Put the Inventory and/or Condition tasks in \'disactivated\' mode',
            4, 12, 3, 4);

        $self->addLabel($grid, '<i>Unprotected objects</i>',
            1, 12, 4, 5);

        my $button3 = $self->addButton($grid, 'Sell all', '', undef,
            2, 4, 5, 6,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('sellall', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Sell all unprotected objects in inventory',
            4, 12, 5, 6);

        my $button4 = $self->addButton($grid, 'Drop all', '', undef,
            2, 4, 6, 7,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('dropall', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Drop all unprotected objects in inventory',
            4, 12, 6, 7);

        my $button5 = $self->addButton($grid, 'Use all', '', undef,
            2, 4, 7, 8,
            TRUE,           # Irreversible
        );
        $self->addLabel($grid, 'Apply the command below to all unprotected objects in inventory',
            4, 12, 7, 8);

        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            2, 12, 8, 9);
        $button5->signal_connect('clicked' => sub {

            my $cmd;

            if ($self->checkEntryIcon($entry)) {

                $cmd = $entry->get_text();

                $self->session->pseudoCmd('useall ' . $cmd, $self->pseudoCmdMode);
            }
        });

        $self->addLabel(
            $grid,
            '<i>NB \'@\' is replaced with each object; e.g. \'put @ in pocket\' becomes \'put'
            . ' rocket in pocket\', etc</i>',
            2, 12, 9, 10);

        # Tab complete
        return 1;
    }

    sub actionsInventory2Tab {

        # Actions Inventory2 tab (also used with Condition task)
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            @columnList,
            %objHash,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsInventory2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Current inventory'],
        );

        # Current inventory
        $self->addLabel($grid, '<b>Current inventory</b>',
            0, 12, 0, 1);

        $self->addLabel($grid,
            '<i>List of objects in character\'s inventory, according to the Inventory task</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Inv type', 'text',              # ->inventoryType
            'Category', 'text',         # ->category
            'Main noun', 'text',        # ->noun
            'Other nouns', 'text',      # ->otherNounList
            'Adjs', 'text',             # ->adjList
            'Pseudo adjs', 'text',      # ->pseudoAdjList
            'Unknowns', 'text',         # ->unknownWordList
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 9);

        # Create a hash to link lines in the simple list to objects in the inventory, in the form:
        #   $objHash{line_number} = blessed_reference_to_model_object
        # Each time the simple list is refreshed, the hash is updated
        %objHash = $self->actionsInventory2Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add buttons
        my $button = $self->addButton(
            $grid,
            'Protect selected',
            'Add semi-protection to inventory objects matching the selected one',
            undef,
            1, 3, 9, 10,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($index, $obj, $cmd);

            ($index) = $slWidget->get_selected_indices();
            if (defined $index && exists $objHash{$index}) {

                $obj = $objHash{$index};

                # Prepare a command to send
                $cmd = 'protectobject ' . $self->actionsInventory2Tab_prepareCmd($obj);
                # Send the command
                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);

                # Refresh the simple list
                %objHash = $self->actionsInventory2Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                );
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Monitor selected',
            'Monitor condition of inventory objects matching the selected one',
            undef,
            3, 5, 9, 10,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            my ($index, $obj, $cmd);

            ($index) = $slWidget->get_selected_indices();
            if (defined $index && exists $objHash{$index}) {

                $obj = $objHash{$index};

                # Prepare a command to send
                $cmd = 'monitorobject ' . $self->actionsInventory2Tab_prepareCmd($obj);
                # Send the command
                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);

                # Refresh the simple list
                %objHash = $self->actionsInventory2Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                );
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of objects currently in the character\'s inventory',
            undef,
            10, 12, 9, 10);
        $button3->signal_connect('clicked' => sub {

            # Refresh the simple list
            %objHash = $self->actionsInventory2Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
            );

            # If the Inventory task isn't running, the buttons (except for the 'refresh list'
            #   button) must be insensitive
            if ($self->session->inventoryTask) {
                $self->sensitiseWidgets($button, $button2);
            } else {
                $self->desensitiseWidgets($button, $button2);
            }
        });

        my $button4 = $self->addButton($grid,
            'Protect objects matching:', 'Protect objects matching this list of words', undef,
            1, 4, 10, 11,
            TRUE,           # Irreversible
        );
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 12, 10, 11);
        $button4->signal_connect('clicked' => sub {

            my $wordString = $entry->get_text();

            if ($self->checkEntryIcon($entry)) {

                # Protect objects matching these words
                $self->session->pseudoCmd('protectobject ' . $wordString, $self->pseudoCmdMode);

                $self->resetEntryBoxes($entry);
            }

            # Refresh the simple list
            %objHash = $self->actionsInventory2Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
            );
        });

        my $button5 = $self->addButton($grid,
            'Monitor objects matching:', 'Monitor objects matching this list of words', undef,
            1, 4, 11, 12,
            TRUE,           # Irreversible
        );
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            4, 12, 11, 12);
        $button5->signal_connect('clicked' => sub {

            my $wordString = $entry2->get_text();

            if ($self->checkEntryIcon($entry2)) {

                # Monitor objects matching these words
                $self->session->pseudoCmd('monitorobject ' . $wordString, $self->pseudoCmdMode);

                $self->resetEntryBoxes($entry2);
            }

            # Refresh the simple list
            %objHash = $self->actionsInventory2Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
            );
        });

        # If the Inventory task isn't running, the entries and buttons (except for the 'refresh
        #   list' button) must be insensitive
        if (! $self->session->inventoryTask) {

            $self->desensitiseWidgets($button, $button2);
        }

        # Tab complete
        return 1;
    }

    sub actionsInventory2Tab_prepareCmd {

        # Called by $self->actionsInventory2Tab to prepare the switches used in the client commands
        #   ';protectobject' and ';monitorobject'
        #
        # Expected arguments
        #   $obj    - Blessed reference of the object in the Inventory task's ->inventoryList which
        #               is about to be protected or monitored
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise, a string containing the switch options for one of the commands,
        #       e.g. '-c weapon -n sword -o big'

        my ($self, $obj, $check) = @_;

        # Local variables
        my $string;

        # Check for improper arguments
        if (! defined $obj || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->actionsInventory2Tab_prepareCmd',
                @_,
            );
        }

        # Prepare the switches
        $string = ' -c ' . $obj->category . ' -n ' . $obj->noun;

        if ($obj->otherNounList) {

            foreach my $word ($obj->otherNounList) {

                $string .= ' -o ' . $word;
            }
        }
        if ($obj->adjList) {

            foreach my $word ($obj->adjList) {

                $string .= ' -a ' . $word;
            }
        }
        if ($obj->pseudoAdjList) {

            foreach my $word ($obj->pseudoAdjList) {

                $string .= ' -p ' . $word;
            }
        }
        if ($obj->unknownWordList) {

            foreach my $word ($obj->unknownWordList) {

                $string .= ' -u ' . $word;
            }
        }

        return $string;
    }

    sub actionsInventory2Tab_refreshList {

        # Called by $self->actionsInventory2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   An empty hash on improper arguments
        #   Otherwise, a hash which links lines in the simple list to the objects it contains, and
        #       which replaces the hash %objHash in $self->actionsInventory2Tab_refreshList

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            $count,
            @objList, @dataList,
            %emptyHash, %objHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            $axmud::CLIENT->writeImproper(
                $self->_objClass . '->actionsInventory2Tab_refreshList',
                @_,
            );

            return %emptyHash;
        }

        # Don't display anything if the Inventory task isn't running
        if (! $self->session->inventoryTask) {

            $self->resetListData($slWidget, [], $columns);

        } else {

            # Import the character's current inventory
            @objList = $self->session->inventoryTask->inventoryList;

            # Compile the simple list data, and create a new hash of objects linked to the line on
            #   which they appear
            $count = -1;
            foreach my $obj (@objList) {

                $count++;
                push (@dataList,
                    $obj->inventoryType,
                    $obj->category,
                    $obj->noun,
                    join(' ', $obj->otherNounList),
                    join(' ', $obj->adjList),
                    join(' ', $obj->pseudoAdjList),
                    join(' ', $obj->unknownWordList),
                );

                $objHash{$count} = $obj;
            }

            # Reset the simple list
            $self->resetListData($slWidget, [@dataList], $columns);
        }

        return %objHash;
    }

    sub actionsLocator1Tab {

        # Actions Locator1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsLocator1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Locator task actions (1/2)'],
        );

        # Locator task actions (1/2)
        $self->addLabel($grid, '<b>Locator task actions (1/2)</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, '<i>Resets</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($grid, 'Reset Locator task', '', undef,
            2, 5, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('resetlocatortask', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Reset the locator when it gets confused (or lost)',
            6, 12, 2, 3);

        $self->addLabel($grid, '<i>Co-ordinated moves</i>',
            1, 12, 3, 4);

        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 32,
            2, 4, 4, 5);
        my $button2 = $self->addButton($grid, 'Move', '', undef,
            4, 5, 4, 5,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            my $cmd;

            if ($self->checkEntryIcon($entry)) {

                $cmd = $entry->get_text();
                $self->session->pseudoCmd('movedirection ' . $cmd, $self->pseudoCmdMode);

                # Reset the entry box
                $self->resetEntryBoxes($entry);
            }
        });
        $self->addLabel($grid, 'Send a movement command to the world',
            6, 12, 4, 5);

        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, 32,
            2, 4, 5, 6);
        my $button3 = $self->addButton($grid, 'Relay', '', undef,
            4, 5, 5, 6,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my $cmd;

            if ($self->checkEntryIcon($entry2)) {

                $cmd = $entry->get_text();
                $self->session->pseudoCmd('relaydirection ' . $cmd, $self->pseudoCmdMode);
            }

                # Reset the entry box
                $self->resetEntryBoxes($entry2);
        });
        $self->addLabel($grid, 'Send a non-movement command to the world',
            6, 12, 5, 6);

        $self->addLabel($grid, '<i>Locator wizard</i>',
            1, 12, 6, 7);

        my $button7 = $self->addButton($grid, 'Run Locator wizard...', '', undef,
            2, 5, 7, 8);
        $button7->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('locatorwizard', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Run the Locator task wizard for the current world',
            6, 12, 7, 8);

        # Tab complete
        return 1;
    }

    sub actionsLocator2Tab {

        # Actions Locator2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsLocator2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Locator task actions (2/2)'],
        );

        # Locator task actions (2/2)
        $self->addLabel($grid, '<b>Locator task actions (2/2)</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, '<i>Unknown words</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($grid, 'Toggle word collection', '', undef,
            2, 5, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('collectunknownwords', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Turn collection of unrecognised words on/off',
            6, 12, 2, 3);

        my $button2 = $self->addButton($grid, 'Empty word list', '', undef,
            2, 5, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptyunknownwords', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Empty the list of unrecognised words',
            6, 12, 3, 4);

        my $button3 = $self->addButton($grid, 'Display word list', '', undef,
            2, 5, 4, 5);
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listunknownwords', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Display unrecognised words in the \'main\' window',
            6, 12, 4, 5);

        $self->addLabel($grid, '<i>Contents lines</i>',
            1, 12, 5, 6);

        my $button4 = $self->addButton($grid, 'Toggle contents collection', '', undef,
            2, 5, 6, 7,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('collectcontentslines', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Turn collection of contents lines on/off',
            6, 12, 6, 7);

        my $button5 = $self->addButton($grid, 'Empty contents list', '', undef,
            2, 5, 7, 8,
            TRUE,           # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptycontentslines', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Empty the list of contents lines',
            6, 12, 7, 8);

        my $button6 = $self->addButton($grid, 'Display contents list', '', undef,
            2, 5, 8, 9);
        $button6->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listcontentslines', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Display contents lines in the \'main\' window',
            6, 12, 8, 9);

        # Tab complete
        return 1;
    }

    sub actionsStatus1Tab {

        # Actions Status1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsStatus1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Status task actions'],
        );

        # Status task actions
        $self->addLabel($grid, '<b>Status task actions</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, '<i>Active mode</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($grid, 'Activate Status task', '', undef,
            2, 5, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('activatestatustask', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Put the Status task in \'active\' mode',
            6, 12, 2, 3);

        my $button2 = $self->addButton($grid, 'Disactivate Status task', '', undef,
            2, 5, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('disactivatestatustask', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Put the Status task in \'disactivated\' mode',
            6, 12, 3, 4);

        $self->addLabel($grid, '<i>Counters</i>',
            1, 12, 4, 5);

        my $button3 = $self->addButton($grid, 'Reset all counters', '', undef,
            2, 5, 5, 6,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('resetcounter', $self->pseudoCmdMode);
        });

        @comboList = (
            'Fight counter',
            'Interaction counter',
            'XP counter',
            'Quest counter',
            'Bank/purse counters',
            'Bank counter',
            'Purse counter',
        );

        my $combo = $self->addComboBox($grid, undef, \@comboList, 'Select counter:',
            TRUE,               # No 'undef' value used
            2, 4, 6, 7);
        my $button4 = $self->addButton($grid, 'Reset', '', undef,
            4, 5, 6, 7,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my $counter = $combo->get_active_text();

            if ($counter eq 'Fight counter') {
                $self->session->pseudoCmd('resetcounter -f', $self->pseudoCmdMode);
            } elsif ($counter eq 'Interaction counter') {
                $self->session->pseudoCmd('resetcounter -i', $self->pseudoCmdMode);
            } elsif ($counter eq 'XP counter') {
                $self->session->pseudoCmd('resetcounter -x', $self->pseudoCmdMode);
            } elsif ($counter eq 'Quest counter') {
                $self->session->pseudoCmd('resetcounter -q', $self->pseudoCmdMode);
            } elsif ($counter eq 'Bank/purse counters') {
                $self->session->pseudoCmd('resetcounter -m', $self->pseudoCmdMode);
            } elsif ($counter eq 'Bank counter') {
                $self->session->pseudoCmd('resetcounter -b', $self->pseudoCmdMode);
            } elsif ($counter eq 'Purse counter') {
                $self->session->pseudoCmd('resetcounter -p', $self->pseudoCmdMode);
            }
        });
        $self->addLabel($grid, 'Reset selected counters',
            6, 12, 6, 7);

        if ($self->session->currentChar) {

            my $entry = $self->addEntryWithIcon(
                $grid, undef, 'int', 0, $self->session->currentChar->constLocalWimpyMax,
                2, 4, 8, 9);
            my $button5 = $self->addButton($grid, 'Set', '', undef,
                4, 5, 8, 9,
                TRUE,           # Irreversible
            );
            $button5->signal_connect('clicked' => sub {

                my $number;

                if ($self->checkEntryIcon($entry)) {

                    $number = $entry->get_text();

                    $self->session->pseudoCmd('setwimpy ' . $number, $self->pseudoCmdMode);
                }
            });
            $self->addLabel($grid, 'Set local wimpy level (0..100)',
                6, 12, 8, 9);

            my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 0, undef,
                2, 4, 9, 10);
            my $button6 = $self->addButton($grid, 'Set', '', undef,
                4, 5, 9, 10,
                TRUE,           # Irreversible
            );
            $button6->signal_connect('clicked' => sub {

                my $number;

                if ($self->checkEntryIcon($entry2)) {

                    $number = $entry2->get_text();

                    $self->session->pseudoCmd('setwimpy -r ' . $number, $self->pseudoCmdMode);
                }
            });
            $self->addLabel($grid, 'Set remote wimpy level (0..max)',
                6, 12, 9, 10);
        }

        # Tab complete
        return 1;
    }

    sub actionsWatch1Tab {

        # Actions Watch1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->actionsWatch1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Watch task actions'],
        );

        # Watch task actions
        $self->addLabel($grid, '<b>Watch task actions</b>',
            0, 12, 0, 1);

        my $button = $self->addButton($grid, 'Empy window', '', undef,
            1, 5, 1, 2,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptywatchwindow', $self->pseudoCmdMode);
        });
        $self->addLabel($grid, 'Empty the task window',
            6, 12, 1, 2);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Toolbar;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Toolbar')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Toolbar button properties'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

#   sub expandNotebook {}       # Inherited from GA::Generic::ConfigWin

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Toolbar button properties
        $self->addLabel($grid, '<b>Toolbar button properties</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2);

        $self->addLabel($grid, 'Description (max 32 chars)',
            1, 3, 2, 3);
        if (! $self->editObj->customFlag) {

            my $entry = $self->addEntry($grid, 'descrip', FALSE,
                3, 12, 2, 3);

        } else {

            $self->addEntryWithIcon($grid, 'descrip', 'string', 1, 32,
                3, 12, 2, 3);
        }

        $self->addLabel($grid, 'Instruction',
            1, 3, 3, 4);
        if (! $self->editObj->customFlag) {

            my $entry = $self->addEntry($grid, 'descrip', FALSE,
                3, 12, 3, 4);

        } else {

            $self->addEntryWithIcon($grid, 'instruct', 'string', 1, 32,
                3, 12, 3, 4);
        }

        $self->addLabel($grid, 'Icon file (24x24 pixels)',
            1, 3, 4, 5);
        my $entry = $self->addEntry($grid, 'iconPath', FALSE,
            3, 10, 4, 5);
        $entry->set_text($self->editObj->iconPath);
        my $button = $self->addButton(
            $grid,
            'Set',
            'Set the icon file used for this toolbar button',
            undef,
            10, 12, 4, 5);
        # (Button desensitised if this isn't a custom toolbar button: default buttons can't have
        #   their icons changed)
        if (! $self->editObj->customFlag) {

            $button->set_sensitive(FALSE);
        }
        $button->signal_connect('clicked' => sub {

            my $path = $self->showFileChooser(
                'Choose file',
                'open',
                $axmud::SHARE_DIR . '/icons/custom',
            );

            if ($path) {

                $self->ivPoke('editHash', 'iconPath', $path);
                $entry->set_text($path);
            }
        });

        $self->addCheckButton($grid, 'Custom toolbar button', 'customFlag', FALSE,
            1, 4, 5, 6);
        $self->addCheckButton($grid, 'Require a current session', 'requireSessionFlag', FALSE,
            4, 8, 5, 6);
        my $checkButton = $self->addCheckButton(
            $grid, 'Require connection to a world', 'requireConnectFlag', TRUE,
            8, 12, 5, 6);
        # (Checkbutton desensitised if this isn't a custom toolbar button)
        if (! $self->editObj->customFlag) {

            $checkButton->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::TTS;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Tts')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Text-to-speech configuration'],
        );

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->patternsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Local variables
        my @comboList;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Text-to-speech configuration
        $self->addLabel($grid, '<b>Text-to-speech configuration</b>',
            0, 12, 0, 1);

        $self->addLabel($grid, 'Name',
            1, 6, 1, 2);
        my $entry = $self->addEntry($grid, 'name', FALSE,
            6, 12, 1, 2);
        my $checkButton = $self->addCheckButton($grid, 'Modifiable', undef, FALSE,
            6, 9, 2, 3);
        my $checkButton2 = $self->addCheckButton($grid, 'Deletable', undef, FALSE,
            9, 12, 2, 3);
        if ($axmud::CLIENT->ivExists('constTtsFixedObjHash', $self->editObj->name)) {

            $checkButton->set_active(FALSE);    # Not modifiable
            $checkButton2->set_active(FALSE);   # Not deletable

        } elsif ($axmud::CLIENT->ivExists('constTtsPermObjHash', $self->editObj->name)) {

            $checkButton->set_active(TRUE);     # Modifiable
            $checkButton2->set_active(FALSE);   # Not deletable

        } else {

            $checkButton->set_active(TRUE);     # Modifiable
            $checkButton2->set_active(TRUE);    # Deletable
        }

        my $label = $self->addLabel(
            $grid,
            'Engine name (must be installed on your system)',
            1, 6, 3, 4);

        @comboList = $axmud::CLIENT->constTTSList;
        my $comboBox = $self->addComboBox($grid, 'engine', \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 12, 3, 4);
        # (->signal_connect appears below)

        # Would like to call ->addEntryWithButton, so the values can be set to 'undef', but without
        #   rewriting that function it's not possible to desensitise both the entry and its 'undef'
        #   button at the same time. Solution is to use our own 'undef' buttons
        my $label2 = $self->addLabel($grid, 'Voice',
            1, 6, 4, 5);
        my $entry2 = $self->addEntry($grid, 'voice', TRUE,
            6, 10, 4, 5);
        my $button2 = $self->addButton(
            $grid, 'Reset', 'Set this value to \'undef\'', undef,
            10, 12, 4, 5);
        $button2->signal_connect('clicked' => sub {

            $entry2->set_text('');
            $self->ivAdd('editHash', 'voice', undef);
        });

        my $label3 = $self->addLabel($grid, 'Speed',
            1, 6, 5, 6);
        my $entry3 = $self->addEntry($grid, 'speed', TRUE,
            6, 10, 5, 6);
        my $button3 = $self->addButton(
            $grid, 'Reset', 'Set this value to \'undef\'', undef,
            10, 12, 5, 6);
        $button3->signal_connect('clicked' => sub {

            $entry3->set_text('');
            $self->ivAdd('editHash', 'speed', undef);
        });

        my $label4 = $self->addLabel($grid, 'Rate',
            1, 6, 6, 7);
        my $entry4 = $self->addEntry($grid, 'rate', TRUE,
            6, 10, 6, 7);
        my $button4 = $self->addButton(
            $grid, 'Reset', 'Set this value to \'undef\'', undef,
            10, 12, 6, 7);
        $button4->signal_connect('clicked' => sub {

            $entry4->set_text('');
            $self->ivAdd('editHash', 'rate', undef);
        });

        my $label5 = $self->addLabel($grid, 'Pitch',
            1, 6, 7, 8);
        my $entry5 = $self->addEntry($grid, 'pitch', TRUE,
            6, 10, 7, 8);
        my $button5 = $self->addButton(
            $grid, 'Reset', 'Set this value to \'undef\'', undef,
            10, 12, 7, 8);
        $button5->signal_connect('clicked' => sub {

            $entry5->set_text('');
            $self->ivAdd('editHash', 'pitch', undef);
        });

        my $label6 = $self->addLabel($grid, 'Volume',
            1, 6, 8, 9);
        my $entry6 = $self->addEntry($grid, 'volume', TRUE,
            6, 10, 8, 9);
        my $button6 = $self->addButton(
            $grid, 'Reset', 'Set this value to \'undef\'', undef,
            10, 12, 8, 9);
        $button6->signal_connect('clicked' => sub {

            $entry6->set_text('');
            $self->ivAdd('editHash', 'volume', undef);
        });

        my $entry7 = $self->addEntry($grid, undef, TRUE,
            1, 10, 9, 10);
        $entry7->set_text('Hello, my name is ' . $axmud::SCRIPT . ' and I am your mud client.');

        my $button7 = $self->addButton(
            $grid, 'Test settings', 'Test these TTS settings', undef,
            10, 12, 9, 10);
        $button7->signal_connect('clicked' => sub {

            my ($engine, $voice, $speed, $rate, $pitch, $volume, $text, $cmd);

            $engine = $comboBox->get_active_text();
            $voice = $entry2->get_text();
            $speed = $entry3->get_text();
            $rate = $entry4->get_text();
            $pitch = $entry5->get_text();
            $volume = $entry6->get_text();
            $text = $entry7->get_text();

            # Prepare the client command
            $cmd = 'speak';
            if ($text) {

                $cmd .= ' <' . $text . '>';
            }

            if ($engine) {

                $cmd .= ' -e ' . $engine;
            }

            if ($voice) {

                $cmd .= ' -v ' . $voice;
            }

            if ($speed) {

                $cmd .= ' -s ' . $speed;
            }

            if ($rate) {

                $cmd .= ' -r ' . $rate;
            }

            if ($pitch) {

                $cmd .= ' -p ' . $pitch;
            }

            if ($volume) {

                $cmd .= ' -l ' . $volume;
            }

            $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);
        });

        # Sensitise entries and reset label texts, depending on the TTS engine
        $self->nameTab_updateWidgets(
            $comboBox->get_active_text(),
            $label2, $label3, $label4, $label5, $label6,
            $entry2, $entry3, $entry4, $entry5, $entry6,
            $button2, $button3, $button4, $button5, $button6,
        );

        # (->signal_connect from above)
        $comboBox->signal_connect('changed' => sub {

            my ($engine, $ttsObj);

            $engine = $comboBox->get_active_text();

            # Get the TTS configuration object with the same name as the engine, so we can copy its
            #   default settings
            $ttsObj = $axmud::CLIENT->ivShow('ttsObjHash', $engine);
            if ($ttsObj) {

                if (defined $ttsObj->voice) {

                    $entry2->set_text($ttsObj->voice);

                } else {

                    $entry2->set_text('');
                    $self->ivAdd('editHash', 'voice', undef);
                }

                if (defined $ttsObj->speed) {

                    $entry3->set_text($ttsObj->speed);

                } else {

                    $entry3->set_text('');
                    $self->ivAdd('editHash', 'speed', undef);
                }

                if (defined $ttsObj->rate) {

                    $entry4->set_text($ttsObj->rate);

                } else {

                    $entry4->set_text('');
                    $self->ivAdd('editHash', 'rate', undef);
                }

                if (defined $ttsObj->pitch) {

                    $entry5->set_text($ttsObj->pitch);

                } else {

                    $entry5->set_text('');
                    $self->ivAdd('editHash', 'pitch', undef);
                }

                if (defined $ttsObj->volume) {

                    $entry6->set_text($ttsObj->volume);

                } else {

                    $entry6->set_text('');
                    $self->ivAdd('editHash', 'volume', undef);
                }

                # Sensitise entries and reset label texts, depending on the TTS engine
                $self->nameTab_updateWidgets(
                    $comboBox->get_active_text(),
                    $label2, $label3, $label4, $label5, $label6,
                    $entry2, $entry3, $entry4, $entry5, $entry6,
                    $button2, $button3, $button4, $button5, $button6,
                );
            }
        });

        # If this configuration isn't modifiable, then neither the combo nor the entries should be
        #   sensitive
        if ($axmud::CLIENT->ivExists('constTtsFixedObjHash', $self->editObj->name)) {

            $comboBox->set_sensitive(FALSE);

            $entry2->set_sensitive(FALSE);
            $entry3->set_sensitive(FALSE);
            $entry4->set_sensitive(FALSE);
            $entry5->set_sensitive(FALSE);
            $entry6->set_sensitive(FALSE);

            $button2->set_sensitive(FALSE);
            $button3->set_sensitive(FALSE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(FALSE);
            $button6->set_sensitive(FALSE);

        }

        # Tab complete
        return 1;
    }

    sub nameTab_updateWidgets {

        # Called by $self->nameTab to sensitive/desensitise entries and reset label texts
        #
        # Expected arguments
        #   $engine                     - The TTS engine displayed
        #   $label2, $label3, $label4, $label5, $label6
        #                               - Five Gtk3::Labels to set/reset
        #   $entry2, $entry3, $entry4, $entry5, $entry6
        #                               - Five Gtk3::Entry boxes to set/reset
        #   $button2, $button3, $button4, $button5, $button6
        #                               - Five Gtk3::Buttons to set/reset
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my (
            $self, $engine, $label2, $label3, $label4, $label5, $label6, $entry2, $entry3, $entry4,
            $entry5, $entry6, $button2, $button3, $button4, $button5, $button6, $check,
        ) = @_;

        # Check for improper arguments
        if (
            ! defined $engine || ! defined $label2 || ! defined $label3 || ! defined $label4
            || ! defined $label5 || ! defined $label6 || ! defined $entry2 || ! defined $entry3
            || ! defined $entry4 || ! defined $entry5 || ! defined $entry6 || ! defined $button2
            || ! defined $button3 || ! defined $button4 || ! defined $button5 || ! defined $button6
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab_updateWidgets', @_);
        }

        # Sensitise/de-sensitise entry boxes, as appropriate ; also update the text of accompanying
        #   labels
        if ($engine eq 'espeak') {

            $entry2->set_sensitive(TRUE);
            $entry3->set_sensitive(TRUE);
            $entry4->set_sensitive(FALSE);
            $entry5->set_sensitive(TRUE);
            $entry6->set_sensitive(FALSE);

            $button2->set_sensitive(TRUE);
            $button3->set_sensitive(TRUE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(TRUE);
            $button6->set_sensitive(FALSE);

            $label2->set_markup('Voice (e.g.  <b>english_rp  english-us  en-scottish</b>  )');
            $label3->set_markup('Word speed (words per minute; 0-100, 0 is default)');
            $label4->set_markup('Word rate (not available with eSpeak)');
            $label5->set_markup('Word pitch (value in the range 0 - 100)');
            $label6->set_markup('Volume (not available with eSpeak)');

        } elsif ($engine eq 'esng') {

            $entry2->set_sensitive(TRUE);
            $entry3->set_sensitive(TRUE);
            $entry4->set_sensitive(FALSE);
            $entry5->set_sensitive(TRUE);
            $entry6->set_sensitive(TRUE);

            $button2->set_sensitive(TRUE);
            $button3->set_sensitive(TRUE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(TRUE);
            $button6->set_sensitive(TRUE);

            $label2->set_markup('Voice (e.g.  <b>en  es  fr</b>  )');
            $label3->set_markup('Word speed (words per minute; value in range 0-100)');
            $label4->set_markup('Word rate (not available with espeak-ng)');
            $label5->set_markup('Word pitch (value in the range 0-100)');
            $label6->set_markup('Volume (value in the range 0-100)');

        } elsif ($engine eq 'flite') {

            $entry2->set_sensitive(TRUE);
            $entry3->set_sensitive(FALSE);
            $entry4->set_sensitive(FALSE);
            $entry5->set_sensitive(FALSE);
            $entry6->set_sensitive(FALSE);

            $button2->set_sensitive(TRUE);
            $button3->set_sensitive(FALSE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(FALSE);
            $button6->set_sensitive(FALSE);

            $label2->set_markup('Voice (e.g.  <b>kal  kal16  awb  rms  slt</b>  )');
            $label3->set_markup('Word speed (not available with Flite)');
            $label4->set_markup('Word rate (not available with Flite)');
            $label5->set_markup('Word pitch (not available with Flite)');
            $label6->set_markup('Volume (not available with Flite)');

        } elsif ($engine eq 'festival') {

            $entry2->set_sensitive(TRUE);
            $entry3->set_sensitive(FALSE);
            $entry4->set_sensitive(TRUE);
            $entry5->set_sensitive(FALSE);
            $entry6->set_sensitive(TRUE);

            $button2->set_sensitive(TRUE);
            $button3->set_sensitive(FALSE);
            $button4->set_sensitive(TRUE);
            $button5->set_sensitive(FALSE);
            $button6->set_sensitive(TRUE);

            $label2->set_markup('Voice (e.g.  <b>voice_kal_diphone  voice_rab_diphone</b>  )');
            $label3->set_markup('Word speed (not available with Festival)');
            $label4->set_markup('Word rate (value in the range 0-100)');
            $label5->set_markup('Word pitch (not available with Festival)');
            $label6->set_markup('Volume (value in the range 0-100)');

        } elsif ($engine eq 'swift') {

            if ($^O eq 'MSWin32') {

                $entry2->set_sensitive(TRUE);
                $entry3->set_sensitive(TRUE);
                $entry4->set_sensitive(FALSE);
                $entry5->set_sensitive(TRUE);
                $entry6->set_sensitive(TRUE);

                $button2->set_sensitive(TRUE);
                $button3->set_sensitive(TRUE);
                $button4->set_sensitive(FALSE);
                $button5->set_sensitive(TRUE);
                $button6->set_sensitive(TRUE);

                $label2->set_markup('Voice (e.g.  <b>Allison  David</b>  )');
                $label3->set_markup('Word speed (words per minute; value in range 0-100)');
                $label4->set_markup('Word rate (not available with Swift)');
                $label5->set_markup('Word pitch (value in the range 0.1 - 5)');
                $label6->set_markup('Volume (value in the range 0-100)');

            } else {

                $entry2->set_sensitive(TRUE);
                $entry3->set_sensitive(FALSE);
                $entry4->set_sensitive(TRUE);
                $entry5->set_sensitive(TRUE);
                $entry6->set_sensitive(TRUE);

                $button2->set_sensitive(TRUE);
                $button3->set_sensitive(FALSE);
                $button4->set_sensitive(TRUE);
                $button5->set_sensitive(TRUE);
                $button6->set_sensitive(TRUE);

                $label2->set_markup('Voice (e.g.  <b>Allison  David</b>  )');
                $label3->set_markup('Word speed (not available with Swift)');
                $label4->set_markup('Word rate (value in the range 0-100)');
                $label5->set_markup('Word pitch (value in the range 0-100)');
                $label6->set_markup('Volume (value in the range 0-100)');
            }

        } elsif ($engine eq 'none') {

            $entry2->set_sensitive(FALSE);
            $entry3->set_sensitive(FALSE);
            $entry4->set_sensitive(FALSE);
            $entry5->set_sensitive(FALSE);
            $entry6->set_sensitive(FALSE);

            $button2->set_sensitive(FALSE);
            $button3->set_sensitive(FALSE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(FALSE);
            $button6->set_sensitive(FALSE);

            $label2->set_markup('Voice');
            $label3->set_markup('Word speed');
            $label4->set_markup('Word rate');
            $label5->set_markup('Word pitch');
            $label6->set_markup('Volume');
        }

        return 1;
    }

    sub patternsTab {

        # Patterns tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $flag;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->patternsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Patterns',
            ['Text-to-speech patterns'],
        );

        # Text-to-speech patterns
        $self->addLabel($grid, '<b>Text-to-speech patterns</b>',
            0, 10, 0, 1);
        $self->addRegexButton($grid,
            [
                'list', 'exclusiveList',
                'list', 'excludedList',
            ],
            10, 12, 0, 1);

        # If this configuration isn't modifiable, then neither the combo nor the entries should be
        #   sensitive
        if ($axmud::CLIENT->ivExists('constTtsFixedObjHash', $self->editObj->name)) {
            $flag = FALSE;
        } else {
            $flag = TRUE;
        }

        $self->addLabel(
            $grid,
            '<i>Exclusive patterns: only convert lines which match these patterns (if not'
            . ' set, all lines converted)</i>',
            1, 12, 1, 2);
        $self->addTextView($grid, 'exclusiveList', $flag,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        $self->addLabel(
            $grid,
            '<i>Excluded patterns: don\'t convert lines which match these patterns'
            . ' (ignored if any exclusive patterns are set)</i>',
            1, 12, 6, 7);
        $self->addTextView($grid, 'excludedList', $flag,
            1, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
        );

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Window;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Generic::Win')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

        my ($self, $hBox, $check) = @_;

        # Local variables
        my @emptyList;

        # Check for improper arguments
        if (! defined $hBox || defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableButtons', @_);
            return @emptyList;
        }

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->numberTab();

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        if ($self->editObj->winCategory eq 'grid') {

            $self->gridTab();
            if (
                $self->editObj->winType eq 'main'
                || $self->editObj->winType eq 'protocol'
                || $self->editObj->winType eq 'custom'
            ) {
                $self->internalTab();
            }

        } else {

            $self->freeTab();
        }

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub numberTab {

        # Number tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $ownerObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->numberTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Number',
            ['Standard window object settings'],
        );

        # Left column
        $self->addLabel($grid, '<b>Standard window object settings</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Window number',
            1, 3, 1, 2);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 1, 2);
        $self->addLabel($grid, 'Window category',
            1, 3, 2, 3);
        $self->addEntry($grid, 'winCategory', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'Window type',
            1, 3, 3, 4);
        $self->addEntry($grid, 'winType', FALSE,
            3, 6, 3, 4);
        $self->addLabel($grid, 'Internal window name',
            1, 3, 4, 5);
        $self->addEntry($grid, 'winName', FALSE,
            3, 6, 4, 5);

        $ownerObj = $self->editObj->owner;
        $self->addLabel($grid, 'Owner',
            1, 3, 5, 6);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 5, 6);
        if ($ownerObj->isa('Games::Axmud::Client')) {
            $entry->set_text('Client');
        } elsif ($ownerObj->isa('Games::Axmud::Session')) {
            $entry->set_text('Session #' . $ownerObj->number);
        } elsif ($ownerObj->isa('Games::Axmud::Generic::Task')) {
            $entry->set_text('Task \'' . $ownerObj->prettyName . '\'');
        } else {
            $entry->set_text('Not client/session/task');
        }

        $self->addLabel($grid, 'Session',
            1, 3, 6, 7);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 6, 7);
        $entry2->set_text($self->editObj->session->number);

        $self->addLabel($grid, 'Workspace',
            1, 3, 7, 8);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            3, 6, 7, 8);
        $entry3->set_text($self->editObj->workspaceObj->number);

        $self->addLabel($grid, 'Child \'free\' windows',
            1, 3, 8, 9);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            3, 6, 8, 9);
        $entry4->set_text($self->editObj->ivPairs('childFreeWinHash'));

        # Right column
        # Add an empty label to separate the two columns
        $self->addLabel($grid, '',
            6, 7, 1, 2);

        $self->addCheckButton($grid, 'Window enabled (actually created)', 'enabledFlag', FALSE,
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Window visible on workspace', 'visibleFlag', FALSE,
            7, 12, 2, 3);

        my $checkButton = $self->addCheckButton($grid, 'Gtk3 window set', undef, FALSE,
            7, 12, 3, 4);
        if ($self->editObj->winWidget) {

            $checkButton->set_active(TRUE);
        }

        my $checkButton2 = $self->addCheckButton($grid, 'Packing box set', undef, FALSE,
            7, 12, 4, 5);
        if ($self->editObj->packingBox) {

            $checkButton2->set_active(TRUE);
        }

        # Tab complete
        return 1;
    }

    sub gridTab {

        # Grid tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $areaObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->gridTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Grid',
            ['\'Grid\' window settings'],
        );

        # Left column
        $self->addLabel($grid, '<b>\'Grid\' window settings</b>',
            1, 6, 0, 1);
        $self->addLabel($grid, 'Workspace grid',
            1, 3, 1, 2);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 1, 2);
        if (! $self->editObj->workspaceGridObj) {
            $entry->set_text('(not set)');
        } else {
            $entry->set_text($self->editObj->workspaceGridObj->number);
        }

        $areaObj = $self->editObj->areaObj;
        $self->addLabel($grid, 'Area object',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 2, 3);
        if (! $areaObj) {

            $entry2->set_text('(not set)');

        } else {

            $entry2->set_text($areaObj->number);

            $self->addLabel($grid, 'Zone',
                1, 3, 3, 4);
            my $entry3 = $self->addEntry($grid, undef, FALSE,
                3, 6, 3, 4);
            if (! $areaObj->zoneObj) {
                $entry3->set_text('(not set)');
            } else {
                $entry3->set_text($areaObj->zoneObj->number);
            }

            $self->addLabel($grid, '<i>Position in zone\'s internal grid</i>',
                1, 3, 4, 5);
            $self->addLabel($grid, 'Layer',
                1, 3, 5, 6);
            my $entry4 = $self->addEntry($grid, undef, FALSE,
                3, 6, 5, 6);
            $entry4->set_text($areaObj->layer);

            $self->addLabel($grid, 'Top-left X (blocks)',
                1, 3, 6, 7);
            my $entry5 = $self->addEntry($grid, undef, FALSE,
                3, 6, 6, 7);
            $entry5->set_text($areaObj->leftBlocks);

            $self->addLabel($grid, 'Top-left Y (blocks)',
                1, 3, 7, 8);
            my $entry6 = $self->addEntry($grid, undef, FALSE,
                3, 6, 7, 8);
            $entry6->set_text($areaObj->topBlocks);

            $self->addLabel($grid, 'Bottom-right X (blocks)',
                1, 3, 8, 9);
            my $entry7 = $self->addEntry($grid, undef, FALSE,
                3, 6, 8, 9);
            $entry7->set_text($areaObj->rightBlocks);

            $self->addLabel($grid, 'Bottom-right Y (blocks)',
                1, 3, 9, 10);
            my $entry8 = $self->addEntry($grid, undef, FALSE,
                3, 6, 9, 10);
            $entry8->set_text($areaObj->bottomBlocks);

            $self->addLabel($grid, 'Width (blocks)',
                1, 3, 10, 11);
            my $entry9 = $self->addEntry($grid, undef, FALSE,
                3, 6, 10, 11);
            $entry9->set_text($areaObj->widthBlocks);

            $self->addLabel($grid, 'Height (blocks)',
                1, 3, 11, 12);
            my $entry10 = $self->addEntry($grid, undef, FALSE,
                3, 6, 11, 12);
            $entry10->set_text($areaObj->heightBlocks);
        }

        # Right column
        $self->addLabel($grid, 'Winmap',
            7, 9, 1, 2);
        my $entry10 = $self->addEntry($grid, 'winmap', FALSE,
            9, 12, 1, 2);
        my $button = $self->addButton($grid, 'Edit winmap...', 'Edit this winmap', undef,
            9, 12, 2, 3);
        $button->signal_connect('clicked' => sub {

            my ($name, $obj);

            $name = $entry10->get_text();
            if ($name) {

                $obj = $axmud::CLIENT->ivShow('winmapHash', $name);
                if ($obj) {

                    # Open an 'edit' window to edit the winmap
                    $self->createFreeWin(
                        'Games::Axmud::EditWin::Winmap',
                        $self,
                        $self->session,
                        'Edit winmap \'' . $name . '\'',
                        $obj,
                        FALSE,                          # Not temporary
                    );
                }
            }
        });

        if ($areaObj) {

            $self->addLabel($grid, '<i>Position on workspace</i>',
                7, 9, 4, 5);
            $self->addLabel($grid, 'Top-left X (pixels)',
                7, 9, 5, 6);
            my $entry11 = $self->addEntry($grid, undef, FALSE,
                9, 12, 5, 6);
            $entry11->set_text($areaObj->xPosPixels);

            $self->addLabel($grid, 'Top-left Y (pixels)',
                7, 9, 6, 7);
            my $entry12 = $self->addEntry($grid, undef, FALSE,
                9, 12, 6, 7);
            $entry12->set_text($areaObj->yPosPixels);

            $self->addLabel($grid, 'Width (pixels)',
                7, 9, 7, 8);
            my $entry13 = $self->addEntry($grid, undef, FALSE,
                9, 12, 7, 8);
            $entry13->set_text($areaObj->widthPixels);

            $self->addLabel($grid, 'Height (pixels)',
                7, 9, 8, 9);
            my $entry14 = $self->addEntry($grid, undef, FALSE,
                9, 12, 8, 9);
            $entry14->set_text($areaObj->heightPixels);
        }

        # Tab complete
        return 1;
    }

    sub freeTab {

        # Free tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->freeTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Free',
            ['\'Free\' window settings'],
        );

        # Left column
        $self->addLabel($grid, '<b>\'Free\' window settings</b>',
            1, 6, 0, 1);
        $self->addLabel($grid, 'Default width (pixels)',
            1, 3, 1, 2);
        $self->addEntry($grid, 'widthPixels', FALSE,
            3, 6, 1, 2, 8, 8);
        $self->addLabel($grid, 'Default height (pixels)',
            1, 3, 2, 3);
        $self->addEntry($grid, 'heightPixels', FALSE,
            3, 6, 2, 3, 8, 8);

        $self->addLabel($grid, '<i>Window customisation settings</i>',
            1, 3, 3, 4);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, 'configHash', \@columnList,
            1, 12, 4, 12);

        # Right column
        $self->addLabel($grid, 'Default border size (pixels)',
            7, 9, 1, 2);
        $self->addEntry($grid, 'borderPixels', FALSE,
            9, 12, 1, 2, 8, 8);
        $self->addLabel($grid, 'Default spacing size (pixels)',
            7, 9, 2, 3);
        $self->addEntry($grid, 'spacingPixels', FALSE,
            9, 12, 2, 3, 8, 8);

        # Tab complete
        return 1;
    }

    sub internalTab {

        # Internal tab
        #
        # 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 . '->internalTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Internal');

        # Add tabs to the inner notebook
        $self->internal1Tab($innerNotebook);
        $self->internal2Tab($innerNotebook);

        return 1;
    }

    sub internal1Tab {

        # Internal1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->internal1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['\'Internal\' window settings (1/2)'],
        );

        # 'Internal' window settings (1/2)
        $self->addLabel($grid, '<b>\'Internal\' window settings (1/2)</b>',
            1, 12, 0, 1);
        $self->addLabel($grid, '<i>Strip objects</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'First', 'bool',
            'Visible', 'bool',
            'Number', 'int',
            'Type', 'text',
            'Class', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 4, 10);

        # Initialise the simple list
        $self->internal1Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add some buttons
        my $button = $self->addButton($grid, 'View...', 'View the selected strip object', undef,
            8, 10, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($number, $stripObj);

            ($number) = $self->getSimpleListData($slWidget, 2);
            if (defined $number) {

                # Check the strip object still exists in the window
                $stripObj = $self->editObj->ivShow('stripHash', $number);
                if ($stripObj) {

                    # Open an 'edit' window for the strip object
                    $self->createFreeWin(
                        'Games::Axmud::EditWin::Strip',
                        $self,
                        $self->session,
                        'Edit strip object #' . $stripObj->number,
                        $stripObj,
                        FALSE,                          # Not temporary
                    );
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Update', 'Update the list', undef,
            10, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            $self->internal1Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;;
    }

    sub internal1Tab_refreshList {

        # Resets the simple list displayed by $self->internal1Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->internal1Tab_refreshList',
                @_,
            );
        }

        # Compile the simple list data
        foreach my $stripObj ($self->editObj->stripList) {

            my ($firstObj, $flag);

            $firstObj = $self->editObj->ivShow('firstStripHash', $stripObj->_objClass);
            if ($firstObj && $firstObj eq $stripObj) {
                $flag = TRUE;
            } else {
                $flag = FALSE;
            }

            push (@dataList,
                $flag,
                $stripObj->visibleFlag,
                $stripObj->number,
                $stripObj->type,
                $stripObj->_objClass,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub internal2Tab {

        # Internal2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->internal2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['\'Internal\' window settings (2/2)'],
        );

        # 'Internal' window settings (2/2)
        $self->addLabel($grid, '<b>\'Internal\' window settings (2/2)</b>',
            1, 12, 0, 1);
        $self->addLabel($grid, '<i>Table objects</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Remove', 'bool',
            'Resize', 'bool',
            'Number', 'int',
            'Type', 'text',
            'Class', 'text',
            'Name', 'text',
            'Tablezone', 'int',
            'X', 'int',
            'Y', 'int',
            'Width', 'int',
            'Height', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 4, 10);

        # Initialise the simple list
        $self->internal2Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add some buttons
        my $button = $self->addButton($grid, 'View...', 'View the selected table object', undef,
            8, 10, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($number, $tableObj);

            ($number) = $self->getSimpleListData($slWidget, 2);
            if (defined $number) {

                # Check the table object still exists in the window
                $tableObj = $self->editObj->tableStripObj->ivShow('tableObjHash', $number);
                if ($tableObj) {

                    # Open an 'edit' window for the strip object
                    $self->createFreeWin(
                        'Games::Axmud::EditWin::Table',
                        $self,
                        $self->session,
                        'Edit table object #' . $tableObj->number,
                        $tableObj,
                        FALSE,                          # Not temporary
                    );
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Update', 'Update the list', undef,
            10, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            $self->internal2Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub internal2Tab_refreshList {

        # Resets the simple list displayed by $self->internal2Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->internal2Tab_refreshList',
                @_,
            );
        }

        # Compile the simple list data
        foreach my $tableObj (
            sort {$a->number <=> $b->number}
            ($self->editObj->tableStripObj->ivValues('tableObjHash'))
        ) {
            push (@dataList,
                $tableObj->allowRemoveFlag,
                $tableObj->allowResizeFlag,
                $tableObj->number,
                $tableObj->type,
                $tableObj->_objClass,
                $tableObj->name,
                $tableObj->zoneObj->number,
                $tableObj->zoneObj->left,
                $tableObj->zoneObj->top,
                ($tableObj->zoneObj->right - $tableObj->zoneObj->left + 1),
                ($tableObj->zoneObj->bottom - $tableObj->zoneObj->top + 1),
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Winmap;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Winmap')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Winmap settings'],
        );

        # Winmap 'edit' windows have some non-standard IVs used to store the widgets created in
        #   $self->winzonesTab, so that several ->winzonesTab_XXX functions can retrieve them
        #   easily
        # The values for each IV are set in ->winzonesTab
        # (Based on code from GA::EditWin::Zonemap)
        $self->{borderX} = undef;
        $self->{borderY} = undef;
        $self->{gridSize} = undef;
        $self->{cellWidth} = undef;
        $self->{cellHeight} = undef;
        $self->{clickMode} = undef;

        $self->{canvasWidget} = undef;
        $self->{bgCanvasObj} = undef;
        $self->{gridCanvasObj} = undef;
        $self->{gridCanvasObj2} = undef;
        $self->{selectCanvasObj} = undef;
        $self->{drawnObjList} = [];

        $self->{entryStartX} = undef;
        $self->{entryStartY} = undef;
        $self->{entryStopX} = undef;
        $self->{entryStopY} = undef;
        $self->{addZoneCombo} = undef;
        $self->{addZoneButton} = undef;
        $self->{cancelButton} = undef;
        $self->{mouseStep} = undef;
        $self->{startX} = undef;
        $self->{startY} = undef;
        $self->{stopX} = undef;
        $self->{stopY} = undef;

        $self->{entryCurrentZone} = undef;
        $self->{entryCurrentTable} = undef;
        $self->{frame} = undef;
        $self->{simpleCanvasWidget} = undef;
        $self->{simpleCanvasObj} = undef;
        $self->{simpleCanvasSize} = 30;
        $self->{frame} = undef;
        $self->{editButton} = undef;
        $self->{deleteButton} = undef;

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->stripsTab();
        $self->winzonesTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Winmap settings
        $self->addLabel($grid, '<b>Winmap settings</b>',
            0, 6, 0, 1);
        $self->addLabel($grid, 'Winmap name',
            1, 3, 1, 2);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 1, 2, 16, 16);

        $self->addLabel(
            $grid,
            '<i>List of worlds which use this winmap as their default winmap for \'main\''
            . ' windows</i>',
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'World', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 3, 10);

        # Initialise the list
        $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets. Can't use $self->addSimpleListButtons_hashIV because the 'worldHash'
        #   IV is in the form $hash{$key} = undef, not $hash{$key} = value
        $self->addLabel($grid, 'World',
            1, 2, 10, 11);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('worldProfHash'));
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # no 'undef' value allowed
            2, 4, 10, 11);

        my $button = $self->addButton($grid, 'Add', 'Add this world to the list', undef,
            4, 6, 10, 11);
        $button->signal_connect('clicked' => sub {

            my $world = $combo->get_active_text();
            if ($world) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('worldHash', $world, undef);

                # Refresh the simple list and combo
                $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));
                $combo->set_active(0);
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Delete',
            'Delete the selected world from the list',
            undef,
            6, 8, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my ($world) = $self->getSimpleListData($slWidget, 0);
            if (defined $world) {

                # Delete the key-value pair from the hash
                $self->modifyEditHash_hashIV('worldHash', $world, undef, TRUE);

                # Refresh the simple list and combo
                $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));
                $combo->set_active(0);
            }
        });

        my $button3 = $self->addButton($grid, 'Reset', 'Reset the list', undef,
            8, 10, 10, 11);
        $button3->signal_connect('clicked' => sub {

            # Remove this IV from $self->editHash, so that the IV in $self->editObj takes over
            $self->ivDelete('editHash', 'worldHash');

            # Refresh the simple list and combo
            $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));
            $combo->set_active(0);
        });

        my $button4 = $self->addButton($grid, 'Clear', 'Clear the list', undef,
            10, 12, 10, 11);
        $button4->signal_connect('clicked' => sub {

            # Add an empty hash to $self->editHash
            $self->ivAdd('editHash', 'worldHash', {});

            # Refresh the simple list and combo
            $self->nameTab_refreshList($slWidget, scalar (@columnList / 2));
            $combo->set_active(0);
        });

        # Tab complete
        return 1;
    }

    sub nameTab_refreshList {

        # Called by $self->nameTab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->nameTab_refreshList',
                @_,
            );
        }

        # Import the hash being displayed
        %ivHash = $self->getEditHash_hashIV('worldHash');

        # Compile the simple list data
        @dataList = sort {lc($a) cmp lc($b)} (keys %ivHash);

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub stripsTab {

        # Strips tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $count, $noStripString, $noneString,
            @list, @comboList, @comboList2, @comboList3, @columnList, @columnList2,
            %comboHash, %tempObjHash, %descripHash, %indexHash,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->stripsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Strips',
            ['Strip objects'],
        );

        # Initialise variables
        $noStripString = '(no strip selected)';
        $noneString = '(none for this strip)';
        %comboHash = reverse $axmud::CLIENT->customStripHash;
        # Create a set of temporary strip objects, so we can access their initialisation hashes
        foreach my $package ($axmud::CLIENT->ivKeys('customStripHash')) {

            my $obj = $package->new(-1, 'temp');
            if ($obj) {

                $tempObjHash{$package} = $obj;
            }
        }

        # Strip objects
        $self->addLabel($grid, '<b>Strip objects</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of strip objects for the window, arranged</i>',
            1, 5, 1, 2);

        $count = 0;
        @list = (
            'vertically, from top to bottom (recommended)'          => 'top',
            'vertically, from bottom to top'                        => 'bottom',
            'horizontally, from left to right (not recommended)'    => 'left',
            'horizontally, from right to left (not recommeneded)'   => 'right',
        );

        do {
            my ($descrip, $orientation);

            $descrip = shift @list;
            $orientation = shift @list;

            push (@comboList, $descrip);
            $descripHash{$descrip} = $orientation;
            $indexHash{$orientation} = $count;

            $count++;

        } until (! @list);

        my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # 'undef' value not allowed
            5, 12, 1, 2);
        $comboBox->set_active($indexHash{$self->editObj->orientation});

        # Add a simple list
        @columnList = (
            'Number', 'int',
            'Strip object', 'text',
            'Jealous', 'bool',
            'Initialisation settings (overriding the default ones)', 'text',
        );

        # (Intentionally leave 2 rows empty)
        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 4);

        # Initialise the list
        $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);

        # Add editing widgets
        @comboList2 = $self->stripsTab_getComboList();
        my $comboBox2 = $self->addComboBox($grid, undef, \@comboList2, '',
            TRUE,              # no 'undef' value allowed
            1, 6, 6, 7);

        my $button = $self->addButton(
            $grid,
            'Add to top',
            'Add strip object using specified initialisation settings',
            undef,
            6, 8, 6, 7);

        my $button2 = $self->addButton(
            $grid,
            'Add to bottom',
            'Add strip object using specified initialisation settings',
            undef,
            8, 10, 6, 7);

        my $button3 = $self->addButton(
            $grid,
            'Clear list',
            'Empty the list of strip objects',
            undef,
            10, 12, 6, 7);

        my $button4 = $self->addButton(
            $grid,
            'Move up',
            'Move the selected strip object up one position in the list',
            undef,
            1, 3, 7, 8);

        my $button5 = $self->addButton(
            $grid,
            'Move down',
            'Move the selected strip object down one position in the list',
            undef,
            3, 6, 7, 8);

        my $button6 = $self->addButton(
            $grid,
            'Delete',
            'Delete the selected strip object',
            undef,
            6, 8, 7, 8);

        my $button7 = $self->addButton(
            $grid,
            'Reverse',
            'Reverse the order of strip objects in the list',
            undef,
            8, 10, 7, 8);

        my $button8 = $self->addButton(
            $grid,
            'Reset list',
            'Reset the list of strip objects',
            undef,
            10, 12, 7, 8);

        $self->addLabel(
            $grid,
            '<i>Modify initialisation settings (overriding the default ones) for the selected strip'
            . ' object:</i>',
            1, 12, 8, 9);

        $self->addLabel($grid, 'Initialisation key/value',
            1, 3, 9, 10);
        @comboList3 = ($noStripString);
        my $comboBox3 = $self->addComboBox($grid, undef, \@comboList3, '',
            TRUE,              # no 'undef' value allowed
            3, 6, 9, 10);
        $comboBox3->set_sensitive(FALSE);

        my $entry = $self->addEntry($grid, undef, TRUE,
            6, 10, 9, 10);
        $entry->set_sensitive(FALSE);

        my $button9 = $self->addButton(
            $grid,
            'Use value',
            'Use the specified initialisation key with the value above',
            undef,
            10, 12, 9, 10);
        $button9->set_sensitive(FALSE);

        my $button10 = $self->addButton(
            $grid,
            'Use the default setting instead',
            'Don\'t use the specified initialisation setting',
            undef,
            1, 6, 10, 11);
        $button10->set_sensitive(FALSE);

        my $button11 = $self->addButton(
            $grid,
            'Use \'undef\'',
            'Use the specified initialisation key with an \'undef\' value',
            undef,
            6, 8, 10, 11);
        $button11->set_sensitive(FALSE);

        my $button12 = $self->addButton(
            $grid,
            'Use TRUE',
            'Use the specified initialisation key with a TRUE value',
            undef,
            8, 10, 10, 11);
        $button12->set_sensitive(FALSE);

        my $button13 = $self->addButton(
            $grid,
            'Use FALSE',
            'Use the specified initialisation key with a FALSE value',
            undef,
            10, 12, 10, 11);
        $button13->set_sensitive(FALSE);

        # Sensitise/desensitise the add buttons, depending on whether the specified strip object is
        #   jealous and already in the list
        $self->stripsTab_updateCombo2(
            $comboBox2,
            $button,
            $button2,
            \%comboHash,
            \%tempObjHash,
        );

        # ->signal_connects for all widgets
        $slWidget->signal_connect ('cursor-changed' => sub {

            # Selecting a row in first simple list updates some editing widgets

            # Local variables
            my (
                $number, $package, $fakeObj,
                @ivList,
            );

            ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number) {

                $self->stripsTab_setInitWidgets(
                    $number,
                    $noStripString, $noneString,
                    $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
                    %tempObjHash,
                );
            }
        });

        $comboBox->signal_connect('changed' => sub {

            my $descrip = $comboBox->get_active_text();
            if ($descrip) {

                $self->ivAdd('editHash', 'orientation', $descripHash{$descrip});
            }
        });

        $comboBox2->signal_connect('changed' => sub {

            # Sensitise/desensitise the add buttons, depending on whether the specified strip object
            #   is jealous and already in the list
            $self->stripsTab_updateCombo2(
                $comboBox2,
                $button,
                $button2,
                \%comboHash,
                \%tempObjHash,
            );
        });

        # 'Add to top'
        $button->signal_connect('clicked' => sub {

            my (
                $descrip, $package,
                @ivList,
            );

            $descrip = $comboBox2->get_active_text();
            if ($descrip) {

                $package = $comboHash{$descrip};

                # Add to the list of initial strip objects with the 'undef' argument representing
                #   default initialisation settings
                @ivList = $self->getEditHash_listIV('stripInitList');
                unshift (@ivList, $package, undef);
                $self->ivAdd('editHash', 'stripInitList', \@ivList);

                # Refresh the list of strip objects, and select the new strip object
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
                $slWidget->select(0);

                # Sensitise/desensitise the add buttons, depending on whether the strip object
                #   specified in $comboBox2 is jealous and already in the list
                $self->stripsTab_updateCombo2(
                    $comboBox2,
                    $button,
                    $button2,
                    \%comboHash,
                    \%tempObjHash,
                );

                # Set/reset initialisation widgets
                $self->stripsTab_setInitWidgets(
                    0,
                    $noStripString, $noneString,
                    $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
                    %tempObjHash,
                );
            }
        });

        # 'Add to bottom'
        $button2->signal_connect('clicked' => sub {

            my (
                $descrip, $package, $number,
                @ivList,
            );

            $descrip = $comboBox2->get_active_text();
            if ($descrip) {

                $package = $comboHash{$descrip};

                @ivList = $self->getEditHash_listIV('stripInitList');
                push (@ivList, $package, undef);
                $self->ivAdd('editHash', 'stripInitList', \@ivList);

                # Refresh the list of strip objects, and select the new strip object
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
                $number = ((scalar @list) / 2) - 1;
                $slWidget->select($number);

                # Sensitise/desensitise the add buttons, depending on whether the strip object
                #   specified in $comboBox2 is jealous and already in the list
                $self->stripsTab_updateCombo2(
                    $comboBox2,
                    $button,
                    $button2,
                    \%comboHash,
                    \%tempObjHash,
                );

                # Set/reset initialisation widgets
                $self->stripsTab_setInitWidgets(
                    $number,
                    $noStripString, $noneString,
                    $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
                    %tempObjHash,
                );
            }
        });

        # 'Clear list'
        $button3->signal_connect('clicked' => sub {

            my @emptyList;

            $self->ivAdd('editHash', 'stripInitList', \@emptyList);

            # Refresh the list of strip objects
            $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);

            # Sensitise/desensitise the add buttons, depending on whether the strip object
            #   specified in $comboBox2 is jealous and already in the list
            $self->stripsTab_updateCombo2(
                $comboBox2,
                $button,
                $button2,
                \%comboHash,
                \%tempObjHash,
            );

            # No strip object is selected in the simple list, so reset initialisation widgets
            $self->stripsTab_resetInitWidgets(
                $noStripString, $noneString,
                FALSE,
                $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
            );
        });

        # 'Move up'
        $button4->signal_connect('clicked' => sub {

            my (
                $number, $package, $hashRef,
                @ivList,
            );

            ($number) = $self->getSimpleListData($slWidget, 0);
            # (Don't move a strip already at the top of the list...)
            if (defined $number && $number > 0) {

                @ivList = $self->getEditHash_listIV('stripInitList');
                ($package, $hashRef) = splice(@ivList, ($number * 2), 2);
                splice(@ivList, (($number * 2) - 2), 0, $package, $hashRef);
                $self->ivAdd('editHash', 'stripInitList', \@ivList);

                # Refresh the list of strip objects
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);

                # The strip should still be highlighted, after being moved up
                $slWidget->select($number - 1);
            }
        });

        # 'Move down'
        $button5->signal_connect('clicked' => sub {

            my (
                $number, $package, $hashRef,
                @ivList,
            );

            @ivList = $self->getEditHash_listIV('stripInitList');

            ($number) = $self->getSimpleListData($slWidget, 0);
            # (Don't move a strip already at the bottom of the list...)
            if (defined $number && ($number * 2) < ((scalar @ivList) - 2)) {

                ($package, $hashRef) = splice(@ivList, ($number * 2), 2);
                splice(@ivList, (($number * 2) + 2), 0, $package, $hashRef);
                $self->ivAdd('editHash', 'stripInitList', \@ivList);

                # Refresh the list of strip objects
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);

                # The strip should still be highlighted, after being moved down
                $slWidget->select($number + 1);
            }
        });

        # 'Delete'
        $button6->signal_connect('clicked' => sub {

            my (
                $number,
                @ivList,
            );

            ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number) {

                @ivList = $self->getEditHash_listIV('stripInitList');
                splice(@ivList, ($number * 2), 2);
                $self->ivAdd('editHash', 'stripInitList', \@ivList);

                # Refresh the list of strip objects
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);

                # Sensitise/desensitise the add buttons, depending on whether the strip object
                #   specified in $comboBox2 is jealous and already in the list
                $self->stripsTab_updateCombo2(
                    $comboBox2,
                    $button,
                    $button2,
                    \%comboHash,
                    \%tempObjHash,
                );

                # No strip object is selected in the simple list, so reset initialisation widgets
                $self->stripsTab_resetInitWidgets(
                    $noStripString, $noneString,
                    FALSE,
                    $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
                );
            }
        });

        # 'Reverse'
        $button7->signal_connect('clicked' => sub {

            my (@ivList, @modList);

            @ivList = $self->getEditHash_listIV('stripInitList');

            if (@ivList) {

                do {

                    my ($package, $hashRef);

                    $package = shift @ivList;
                    $hashRef = shift @ivList;

                    unshift(@modList, $hashRef);
                    unshift(@modList, $package);

                } until (! @ivList);

                $self->ivAdd('editHash', 'stripInitList', \@modList);

                # Refresh the list of strip objects
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
            }
        });

        # 'Reset list'
        $button8->signal_connect('clicked' => sub {

            $self->ivDelete('editHash', 'stripInitList');

            # Refresh the list of strip objects
            $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);

            # Sensitise/desensitise the add buttons, depending on whether the strip object
            #   specified in $comboBox2 is jealous and already in the list
            $self->stripsTab_updateCombo2(
                $comboBox2,
                $button,
                $button2,
                \%comboHash,
                \%tempObjHash,
            );

            # No strip object is selected in the simple list, so reset initialisation widgets
            $self->stripsTab_resetInitWidgets(
                $noStripString, $noneString,
                FALSE,
                $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
            );
        });

        # Key/value combobox
        $comboBox3->signal_connect('changed' => sub {

            my $key = $comboBox3->get_active_text();
            if ($key && $key ne $noStripString && $key ne $noneString) {

                # Sensitise/desensitise widgets, depending on whether this key is a flag, or not
                if ($key =~ m/_flag$/) {

                    $self->desensitiseWidgets($entry, $button9, $button11);
                    $self->sensitiseWidgets($button12, $button13);

                } else {

                    $self->sensitiseWidgets($entry, $button9, $button11);
                    $self->desensitiseWidgets($button12, $button13);
                }
            }
        });

        # 'Use value'
        $button9->signal_connect('clicked' => sub {

            my ($number, $key, $value);

            ($number) = $self->getSimpleListData($slWidget, 0);
            $key = $comboBox3->get_active_text();
            $value = $entry->get_text();

            if (defined $number && $key) {

                $self->stripsTab_updateStripList($number, $key, $value);

                # Refresh the list of strip objects, retaining the same selected row
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
                $slWidget->select($number);

                # Reset the entry/combo boxes for visual confirmation that the key/value pair was
                #   added
                $comboBox3->set_active(0);
                $entry->set_text('');
            }
        });

        # 'Use the default setting instead'
        $button10->signal_connect('clicked' => sub {

            my (
                $number, $key, $hashRef,
                @ivList,
            );

            ($number) = $self->getSimpleListData($slWidget, 0);
            $key = $comboBox3->get_active_text();

            if (defined $number && $key) {

                @ivList = $self->getEditHash_listIV('stripInitList');
                $hashRef = $ivList[($number * 2) + 1];
                if (defined $hashRef) {

                    delete $$hashRef{$key}
                }

                splice(@ivList, (($number * 2) + 1), 1, $hashRef);

                $self->ivAdd('editHash', 'stripInitList', \@ivList);

                # Refresh the list of strip objects, retaining the same selected row
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
                $slWidget->select($number);

                # Reset the entry/combo boxes for visual confirmation that the key/value pair was
                #   removed
                $comboBox3->set_active(0);
                $entry->set_text('');
            }
        });

        # 'Use undef'
        $button11->signal_connect('clicked' => sub {

            my ($number, $key, $value);

            ($number) = $self->getSimpleListData($slWidget, 0);
            $key = $comboBox3->get_active_text();

            if (defined $number && $key) {

                $self->stripsTab_updateStripList($number, $key, undef);

                # Refresh the list of strip objects, retaining the same selected row
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
                $slWidget->select($number);

                # Reset the entry/combo boxes for visual confirmation that the key/value pair was
                #   added
                $comboBox3->set_active(0);
                $entry->set_text('');
            }
        });

        # 'Use TRUE'
        $button12->signal_connect('clicked' => sub {

            my ($number, $key, $value);

            ($number) = $self->getSimpleListData($slWidget, 0);
            $key = $comboBox3->get_active_text();

            if (defined $number && $key) {

                $self->stripsTab_updateStripList($number, $key, TRUE);

                # Refresh the list of strip objects, retaining the same selected row
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
                $slWidget->select($number);

                # Reset the entry/combo boxes for visual confirmation that the key/value pair was
                #   added
                $comboBox3->set_active(0);
                $entry->set_text('');
            }
        });

        # 'Use FALSE'
        $button13->signal_connect('clicked' => sub {

            my ($number, $key, $value);

            ($number) = $self->getSimpleListData($slWidget, 0);
            $key = $comboBox3->get_active_text();

            if (defined $number && $key) {

                $self->stripsTab_updateStripList($number, $key, FALSE);

                # Refresh the list of strip objects, retaining the same selected row
                $self->stripsTab_refreshList($slWidget, scalar (@columnList / 2), %tempObjHash);
                $slWidget->select($number);

                # Reset the entry/combo boxes for visual confirmation that the key/value pair was
                #   added
                $comboBox3->set_active(0);
                $entry->set_text('');
            }
        });

        # Tab complete
        return 1;
    }

    sub stripsTab_refreshList {

        # Called by $self->stripsTab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns in the list
        #   %tempObjHash    - A hash of temporary strip objects, one for each package name, so we
        #                       can access their IVs
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, %tempObjHash) = @_;

        # Local variables
        my (
            $count,
            @dataList, @ivList,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! %tempObjHash) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->stripsTab_refreshList',
                @_,
            );
        }

        # Import the list being displayed
        @ivList = $self->getEditHash_listIV('stripInitList');

        # Compile the simple list data
        if (@ivList) {

            $count = 0;

            do {

                my ($package, $hashRef, $string, $tempObj);

                $package = shift @ivList;
                $hashRef = shift @ivList;
                $string = '';
                $tempObj = $tempObjHash{$package};

                if (defined $hashRef) {

                    foreach my $key (sort {lc($a) cmp lc($b)} (keys %$hashRef)) {

                        my $value = $$hashRef{$key};

                        if ($string) {

                            $string .= ', ';
                        }

                        if (! defined $value) {

                            $string .= $key . ':<undef>';

                        } elsif ($key =~ m/_flag$/) {

                            if (! $value) {
                                $string .= $key . ':FALSE';
                            } else {
                                $string .= $key . ':TRUE';
                            }

                        } else {

                            $string .= $key . ':' . $value;
                        }
                    }
                }

                push (@dataList,
                    $count,
                    $axmud::CLIENT->ivShow('customStripHash', $package),
                    $tempObj->jealousyFlag,
                    $string,
                );

                $count++;

            } until (! @ivList);
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub stripsTab_getComboList {

        # Called by $self->stripsTab to populate a combobox
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   An empty list on improper arguments
        #   A list of combobox items otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            @emptyList, @returnList,
            %constHash, %customHash,
        );

        # Check for improper arguments
        if (defined $check) {

            $axmud::CLIENT->writeImproper(
                $self->_objClass . '->stripsTab_getComboList',
                @_,
            );

            return @emptyList;
        }

        # Import the IVs (for convenience)
        %constHash = $axmud::CLIENT->constStripHash;
        %customHash = $axmud::CLIENT->customStripHash;

        # Use everything in GA::Client->customStripHash, but use the built-in strip objects first
        push (@returnList, (sort {lc($a) cmp lc($b)} (values %constHash)));

        foreach my $package (
            sort {lc($customHash{$a}) cmp lc($customHash{$b})} (keys %customHash)
        ) {
            if (! exists $constHash{$package}) {

                push (@returnList, $package, $customHash{$package});
            }
        }

        return @returnList;
    }

    sub stripsTab_updateStripList {

        # Called by $self->stripsTab
        # The ->stripInitList IV is in the form
        #   (package_name, hash_reference, package_name, hash_reference...)
        # This functions modifies an existing hash reference
        #
        # Expected arguments
        #   $number     - The number of the hash reference in the list (first one, $number = 0,
        #                   second one, $number = 1, etc)
        #   $key        - The key to add to the hash reference
        #
        # Optional arguments
        #   $value      - The value to add to the hash reference (can be 'undef')
        #
        # Return values
        #   'undef' improper arguments
        #   1 otherwise

        my ($self, $number, $key, $value, $check) = @_;

        # Local variables
        my (
            $hashRef,
            @ivList,
        );

        # Check for improper arguments
        if (! defined $number || ! defined $key || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->stripsTab_updateStripList',
                @_,
            );
        }

        @ivList = $self->getEditHash_listIV('stripInitList');
        $hashRef = $ivList[($number * 2) + 1];

        if (! defined $hashRef || ! %$hashRef) {

            %$hashRef = ($key, $value);

        } else {

            $$hashRef{$key} = $value;
        }

        splice(@ivList, (($number * 2) + 1), 1, $hashRef);

        $self->ivAdd('editHash', 'stripInitList', \@ivList);

        return 1;
    }

    sub stripsTab_updateCombo2 {

        # Called by $self->stripsTab
        # Sensitise/desensitise the add buttons, depending on whether the strip object specified in
        #   the combobox is jealous and is already in the list IV
        #
        # Expected arguments
        #   $comboBox2, $button, $button2
        #                   - The affected widgets
        #   $comboHashRef   - $axmud::CLIENT->customStripHash, reversed and referenced
        #   $tempObjHashRef - Reference to a hash of temporary strip objects (so we can access their
        #                       initialisation settings, etc)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $comboBox2, $button, $button2, $comboHashRef, $tempObjHashRef, $check) = @_;

        # Local variables
        my ($descrip, $package, $tempObj);

        # Check for improper arguments
        if (
            ! defined $comboBox2 || ! defined $button || ! defined $button2
            || ! defined $comboHashRef || ! defined $tempObjHashRef || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->stripsTab_updateStripList',
                @_,
            );
        }

        $descrip = $comboBox2->get_active_text();
        if ($descrip) {

            $package = $$comboHashRef{$descrip};
            if ($package) {

                $tempObj = $$tempObjHashRef{$package};
                if ($tempObj) {

                    # Sensitise/desensitise the add buttons, depending on whether the strip object
                    #   is jealous and is already in the list
                    if (! $tempObj->jealousyFlag || ! $self->stripsTab_checkPackage($package)) {
                        $self->sensitiseWidgets($button, $button2)
                    } else {
                        $self->desensitiseWidgets($button, $button2)
                    }
                }
            }
        }

        return 1;
    }

    sub stripsTab_setInitWidgets {

        # Called by $self->stripsTab
        # Sets and sensitises/desensitises the initialisation widgets when a strip object in the
        #   simple list is selected
        #
        # Expected arguments
        #   $number         - The number of the selected (matches the position of a two-element
        #                       group in $self->editObj->stripInitList)
        #   $noStripString, $noneString
        #                   - Text to use in the comboboxes
        #   $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13
        #                   - List of widgets to desensitise
        #   %tempObjHash    - A hash of temporary strip objects, one for each package name, so we
        #                       can access their IVs
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my (
            $self, $number, $noStripString, $noneString, $entry, $comboBox3, $button9, $button10,
            $button11, $button12, $button13, %tempObjHash,
        ) = @_;

        # Local variables
        my (
            $package, $tempObj,
            @ivList,
        );

        # Check for improper arguments
        if (
            ! defined $number || ! defined $noStripString || ! defined $noneString
            || ! defined $entry || ! defined $comboBox3 || ! defined $button9
            || ! defined $button10 || ! defined $button11 || ! defined $button12
            || ! defined $button13 || ! %tempObjHash
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->stripsTab_setInitWidgets',
                @_,
            );
        }

        @ivList = $self->getEditHash_listIV('stripInitList');
        $package = $ivList[($number * 2)];
        $tempObj = $tempObjHash{$package};

        if (! $tempObj || ! $tempObj->initHash) {

            # Reset widgets
            if (! $tempObj) {

                $self->stripsTab_resetInitWidgets(
                    $noStripString, $noneString,
                    FALSE,
                    $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
                );

            } else {

                $self->stripsTab_resetInitWidgets(
                    $noStripString, $noneString,
                    TRUE,
                    $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
                );
            }

        } else {

            # Set widgets
            $entry->set_text('');

            $self->sensitiseWidgets(
                $comboBox3, $entry,
                $button9, $button10, $button11, $button12, $button13,
            );

            $self->resetComboBox(
                $comboBox3,
                (sort {lc($a) cmp lc($b)} ($tempObj->ivKeys('initHash'))),
            );
        }

        return 1;
    }

    sub stripsTab_resetInitWidgets {

        # Called by $self->stripsTab
        # Resets and desensitises the initialisation widgets when a strip object in the simple list
        #   is unselected, or when a strip object with an empty initialisation hash is selected
        #
        # Expected arguments
        #   $noStripString, $noneString
        #                   - Text to use in the comboboxes
        #   $flag           - Flag set to TRUE if a strip object is selected in the simple list,
        #                       FALSE if not
        #   $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13
        #                   - List of widgets to desensitise
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my (
            $self, $noStripString, $noneString, $flag, $entry, $comboBox3, $button9, $button10,
            $button11, $button12, $button13, $check,
        ) = @_;

        # Check for improper arguments
        if (
            ! defined $noStripString || ! defined $noneString || ! defined $flag || ! defined $entry
            || ! defined $comboBox3 || ! defined $button9 || ! defined $button10
            || ! defined $button11 || ! defined $button12 || ! defined $button13
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->stripsTab_resetInitWidgets',
                @_,
            );
        }

        # Reset widgets. Using some text in the combo, rather than nothing, reduces widget re-sizing
        $entry->set_text('');

        $self->desensitiseWidgets(
            $entry, $comboBox3, $button9, $button10, $button11, $button12, $button13,
        );

        # (Reset combo last, otherwise the ->signal_connect for the combo desensitises widgets,
        #   which are re-sensitised by this ->signal_connect)
        if (! $flag) {
            $self->resetComboBox($comboBox3, $noStripString);
        } else {
            $self->resetComboBox($comboBox3, $noneString);
        }

        return 1;
    }

    sub stripsTab_checkPackage {

        # Called by $self->stripsTab_updateCombo2
        # Checks whether a strip object package name exists in the ->stripInitList IV, or not
        #
        # Expected arguments
        #   $package    - The package name to check, e.g. 'Games::Axmud::Strip::MenuBar'
        #
        # Return values
        #   'undef' on improper arguments or if the package name is not in ->stripInitList
        #   1 if the package name is found in ->stripInitList

        my ($self, $package, $check) = @_;

        # Local variables
        my @ivList;

        # Check for improper arguments
        if (! defined $package || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->stripsTab_updateStripList',
                @_,
            );
        }

        @ivList = $self->getEditHash_listIV('stripInitList');
        if (@ivList) {

            do {

                my ($thisPackage, $hashRef);

                $thisPackage = shift @ivList;
                $hashRef = shift @ivList;

                if ($thisPackage eq $package) {

                    return 1;
                }

            } until (! @ivList);
        }

        return undef;
    }

    sub winzonesTab {

        # Winzones tab
        #
        # 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 . '->winzonesTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Winzones');

        # Add tabs to the inner notebook
        $self->winzones1Tab($innerNotebook);
        $self->winzones2Tab($innerNotebook);

        return 1;
    }

    sub winzones1Tab {

        # Winzones1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $widthRequest, $heightRequest,
            @comboList,
            %packageHash,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->winzones1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Add a new winzone'],
        );

        # Import IVs (for convenience)
        %packageHash = reverse ($axmud::CLIENT->customTableHash);

        # We're going to use a GooCanvas2::Canvas to draw a grid containing squares in various
        #   colours

        ########
        # Part 1 - set up the canvas, and draw an empty grid

        # Unusual step - we have to save a number of variables as IVs, so that this object's methods
        #   can access them, without passing a million arguments back and forth
        $self->ivPoke('borderX', 10);           # Size of area outside the grid (pixels)
        $self->ivPoke('borderY', 10);           #
        $self->ivPoke('gridSize', 60);          # Grid is 60x60 gridblocks
        $self->ivPoke('cellWidth', 6);          # Size of each gridblock (pixels)
        $self->ivPoke('cellHeight', 5);         #

        # Add a drawing canvas, displaying a 60x60 grid containing any number of zones
        #   $self->winzones1Tab_getMouseClick  - Responds to mouse clicks on the canvas
        #   $self->winzones1Tab_getMouseMotion - Responds to mouse motion over the canvas
        $widthRequest = ($self->borderX * 2) + ($self->gridSize * $self->cellWidth);
        $heightRequest = ($self->borderY * 2) + ($self->gridSize * $self->cellHeight);

        my $canvasWidget = $self->addDrawingCanvas(
            $grid,
            'winzones1Tab_getMouseClick',
            'winzones1Tab_getMouseMotion',
            FALSE, FALSE,                       # No scrolling
            0, 7, 0, 12,                        # Position in grid
            $widthRequest, $heightRequest,      # Requested size of viewport
        );

        # Draw the canvas background (in white)
        my $canvasObj = GooCanvas2::CanvasRect->new(
            'parent' => $canvasWidget->get_root_item(),
            x => 0,
            y => 0,
            width => $widthRequest,
            height => $heightRequest,
#            'line-width' => 2,
            'stroke-color' => '#FFFFFF',
            'fill-color' => '#FFFFFF',
        );

        $canvasObj->lower();

        # Draw a 60x60 grid on the canvas
        my $canvasObj2 = GooCanvas2::CanvasGrid->new(
            'parent' => $canvasWidget->get_root_item(),
            x => $self->borderX,
            y => $self->borderY,
            width => ($self->gridSize * $self->cellWidth),
            height => ($self->gridSize * $self->cellHeight),
            x_step => $self->cellWidth,
            y_step => $self->cellHeight,
            x_offset => 0,
            y_offset => 0,
            'horz-grid-line-width' => 1,
            'horz-grid-line-color' => '#808080',
            'vert-grid-line-width' => 1,
            'vert-grid-line-color' => '#808080',
        );

        # Overlay that with a 12x12 grid in a stronger colour, to clearly show the subvidisions
        my $canvasObj3 = GooCanvas2::CanvasGrid->new(
            'parent' => $canvasWidget->get_root_item(),
            x => $self->borderX,
            y => $self->borderY,
            width => ($self->gridSize * $self->cellWidth),
            height => ($self->gridSize * $self->cellHeight),
            x_step => ($self->cellWidth * 5),
            y_step => ($self->cellHeight * 5),
            x_offset => 0,
            y_offset => 0,
            'horz-grid-line-width' => 1,
            'horz-grid-line-color' => '#000000',
            'vert-grid-line-width' => 1,
            'vert-grid-line-color' => '#000000',
        );

        $canvasObj3->raise();

        # Save more IVs
        $self->ivPoke('canvasWidget', $canvasWidget);
        $self->ivPoke('bgCanvasObj', $canvasObj);
        $self->ivPoke('gridCanvasObj', $canvasObj2);
        $self->ivPoke('gridCanvasObj2', $canvasObj3);

        # Now draw any existing winzones
        $self->winzones1Tab_refreshGrid();

        ########
        # Part 2 - widgets for adding new zones

        $self->addLabel($grid, '<b>Add a new winzone</b>',
            8, 12, 0, 1);

        # Create an IV to store whether we want 1-block clicks or 5-block clicks
        $self->ivPoke('clickMode', 1);  # 0     - 1-block clicks
                                        # 1     - 5-block clicks

        my ($radioGroup, $radioButton, $radioButton2);
        ($radioGroup, $radioButton) = $self->addRadioButton($grid,
            undef, 'Use 5-block clicks', undef, undef, TRUE,
            7, 12, 1, 2);

        ($radioGroup, $radioButton2) = $self->addRadioButton($grid,
            $radioGroup, 'Use 1-block clicks', undef, undef, TRUE,
            7, 12, 2, 3);

        $self->addLabel($grid, 'Top-left corner',
            7, 9, 3, 4);
        my $entry = $self->addEntry($grid, undef, FALSE,
            9, 10, 3, 4, 3, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 3, 4, 3, 3);

        $self->addLabel($grid, 'Bottom-right corner',
            7, 9, 4, 5);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            9, 10, 4, 5, 3, 3);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            10, 12, 4, 5, 3, 3);

        @comboList = (sort {lc($a) cmp lc($b)} (keys %packageHash));
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # no 'undef' value allowed
            7, 12, 5, 6);

        # Save these entry boxes as IVs, so they can be updated as the mouse moves over the drawing
        #   canvas
        $self->ivPoke('entryStartX', $entry);
        $self->ivPoke('entryStartY', $entry2);
        $self->ivPoke('entryStopX', $entry3);
        $self->ivPoke('entryStopY', $entry4);

        # New zones are selected in three steps - click on the top-left corner of the zone, then on
        #   the bottom-right, then on the 'add winzone' button
        my $button = $self->addButton($grid,
            'Add winzone', 'Create a winzone using the selected area', undef,
            7, 9, 6, 7,
            TRUE,           # Irreversible
        );
        my $button2 = $self->addButton($grid,
            'Cancel', 'Don\'t create a winzone using the selected area', undef,
            9, 12, 6, 7);

        # Save the combo/ buttons as IVs, so that they can be sensitised/desensitised (start
        #   desensitised)
        $self->ivPoke('addZoneCombo', $combo);
        $self->ivPoke('addZoneButton', $button);
        $self->ivPoke('cancelButton', $button2);
        $combo->set_sensitive(FALSE);
        $button->set_sensitive(FALSE);
        $button2->set_sensitive(FALSE);

        # We also have to save which step we're on as an IV, so that other functions can use it
        $self->ivPoke('mouseStep', 1);
        # On steps 1 and 2, we save the location of the mouse clicks
        $self->ivPoke('startX', undef);
        $self->ivPoke('startY', undef);
        $self->ivPoke('stopX', undef);
        $self->ivPoke('stopY', undef);

        ########
        # Part 3 - widgets for existing zones

        $self->addLabel($grid, '<b>Winzone actions</b>',
            7, 12, 7, 8);

        # When clicking on an existing zone, the winzone's number is displayed...
        $self->addLabel($grid, 'Selected winzone #',
            7, 9, 8, 9);
        my $entry5 = $self->addEntry($grid, undef, FALSE,
            9, 10, 8, 9, 3, 3);
        $self->ivPoke('entryCurrentZone', $entry5);

        # ...as is its colour
        my ($frame, $simpleCanvasWidget, $simpleCanvasObj) = $self->addSimpleCanvas(
            $grid, undef, undef,
            10, 11, 8, 9,               # Position in grid
            $self->simpleCanvasSize,    # Width request
            $self->simpleCanvasSize,    # Height request
        );
        $self->ivPoke('frame', $frame);
        $self->ivPoke('simpleCanvasWidget', $simpleCanvasWidget);
        $self->ivPoke('simpleCanvasObj', $simpleCanvasObj);

        # (This empty label reduces the size of the simple canvas, so that the whole frame is filled
        #   by a single colour)
        $self->addLabel($grid, '',
            11, 12, 8, 9);

        # Other widgets
        my $entry6 = $self->addEntry($grid, undef, FALSE,
            7, 12, 9, 10);
        $self->ivPoke('entryCurrentTable', $entry6);

        my $button3 = $self->addButton($grid, 'Edit...', 'Edit the selected winzone', undef,
            7, 9, 10, 11);
        my $button4 = $self->addButton($grid, 'Delete', 'Delete the selected winzone', undef,
            9, 12, 10, 11,
            TRUE,           # Irreversible
        );
        my $button5 = $self->addButton($grid,
            'Dump list', 'Display the list of winzones in the \'main\' window', undef,
            7, 9, 11, 12);
        my $button6 = $self->addButton($grid, 'Refresh grid', 'Refresh the winmap grid', undef,
            9, 12, 11, 12);

        # Save the buttons as IVs, so that it can be sensitised/desensitised (starts desensitised.
        #   Don't need to save or desensitise the 'dump' button)
        $self->ivPoke('editButton', $button3);
        $self->ivPoke('deleteButton', $button4);
        $button3->set_sensitive(FALSE);
        $button4->set_sensitive(FALSE);

        ########
        # Part 4 - signal connects

        # Add winzone button
        $button->signal_connect('clicked' => sub {

            # Add the new winzone
            $self->session->pseudoCmd(
                'addwinzone ' . $self->editObj->name
                . ' -p ' . $self->startX . ' ' . $self->startY
                . ' -s ' . ($self->stopX - $self->startX + 1)
                . ' ' . ($self->stopY - $self->startY + 1)
                . ' -n ' . $packageHash{$combo->get_active_text()},
                $self->pseudoCmdMode,
            );

            # We don't know which winzone was created, so re-draw all of them
            # Re-draw the grid and all existing winzones, and return to step 1
            $self->winzones1Tab_refreshGrid();
            $self->winzones1Tab_resetMouseStep();
        });

        # Cancel button
        $button2->signal_connect('clicked' => sub {

            # Re-draw the grid and all existing winzones, and return to step 1
            $self->winzones1Tab_resetMouseStep();
        });

        # Edit/View button
        $button3->signal_connect('clicked' => sub {

            my ($obj, $number);

            # Edit the selected winzone
            if ($self->entryCurrentZone) {

                $number = $self->entryCurrentZone->get_text();
                if (defined $number) {

                    # Get the blessed reference of the winzone
                    $obj = $self->editObj->ivShow('zoneHash', $number);
                    if ($obj) {

                        # Open an 'edit' window for the winzone
                        $self->createFreeWin(
                            'Games::Axmud::EditWin::Winzone',
                            $self,
                            $self->session,
                             'Edit winzone #' . $number,
                            $obj,
                            FALSE,                          # Not temporary
                        );
                    }
                }
            }

            # Re-draw the grid and all existing winzones, and return to step 1
            $self->winzones1Tab_refreshGrid();
            $self->winzones1Tab_resetMouseStep();
        });

        # Delete button
        $button4->signal_connect('clicked' => sub {

            my $number;

            # Delete the selected winzone
            if ($self->entryCurrentZone) {

                $number = $self->entryCurrentZone->get_text();
                if (defined $number) {

                    $self->session->pseudoCmd(
                        'deletewinzone ' . $self->editObj->name . ' ' . $number,
                        $self->pseudoCmdMode,
                    );

                    # Re-draw the grid and all existing winzones, and return to step 1
                    $self->winzones1Tab_refreshGrid();
                    $self->winzones1Tab_resetMouseStep();
                }
            }
        });

        # Dump list button
        $button5->signal_connect('clicked' => sub {

            $self->session->pseudoCmd(
                'listwinzone ' . $self->editObj->name,
                $self->pseudoCmdMode,
            );
        });

        # Refresh grid button
        $button6->signal_connect('clicked' => sub {

            # Re-draw the grid and all existing zones, and return to step 1
            $self->winzones1Tab_resetMouseStep();
        });

        # 5-block / 1-block clicks radio button
        $radioButton->signal_connect('toggled' => sub {

            # Re-draw the grid and all existing zones (including the new one, if it was created),
            #   and return to mouse step 1
            $self->winzones1Tab_resetMouseStep();

            if ($radioButton->get_active()) {
                $self->ivPoke('clickMode', 1);
            } else {
                $self->ivPoke('clickMode', 0);
            }
        });

        # Tab complete
        return 1;
    }

    sub winzones1Tab_resetMouseStep {

        # Called by $self->winzones1Tab when the $self->mouseStep needs to be set back to 1, either
        #   because the user clicked a third time on the drawing canvas, or because they clicked the
        #   'cancel' button, or because they changed from 1-block clicks to 5-block clicks (or
        #   vice-versa)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $canvasObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->winzones1Tab_resetMouseStep',
                @_,
            );
        }

        # Remove the selected block/zone, if drawn
        if ($self->selectCanvasObj) {

            $self->selectCanvasObj->remove();
            $self->ivUndef('selectCanvasObj');
        }

        # Go back to step 1
        $self->ivPoke('mouseStep', 1);
        $self->ivUndef('startX');
        $self->ivUndef('stopX');
        $self->ivUndef('startY');
        $self->ivUndef('stopY');

        # Desensitise the 'Add winzone'/'Cancel'/'Edit'/'Delete' buttons and the combo
        $self->addZoneCombo->set_sensitive(FALSE);
        $self->addZoneButton->set_sensitive(FALSE);
        $self->cancelButton->set_sensitive(FALSE);
        $self->editButton->set_sensitive(FALSE);
        $self->deleteButton->set_sensitive(FALSE);

        # Reset the entry boxes
        $self->entryStartX->set_text('');
        $self->entryStartY->set_text('');
        $self->entryStopX->set_text('');
        $self->entryStopY->set_text('');
        $self->entryCurrentZone->set_text('');
        $self->entryCurrentTable->set_text('');

        # Reset the colour displayed in the simple canvas
        $canvasObj = $self->fillSimpleCanvas(
            $self->simpleCanvasWidget,
            $self->simpleCanvasObj,
            '#FFFFFF',
            $self->simpleCanvasSize,
            $self->simpleCanvasSize,
        );

        $self->ivPoke('simpleCanvasObj', $canvasObj);

        # Move back to the first step
        $self->ivPoke('mouseStep', 1);

        return 1;
    }

    sub winzones1Tab_getMouseClick {

        # Called by ->signal_connect in $self->addDrawingCanvas (inherited from GA::Generic::Win),
        #   whenever the user clicks on the drawing canvas
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $xPos, $yPos    - The coordinates on the drawing canvas at which the user clicked
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $xPos, $yPos, $check) = @_;

        # Local variables
        my (
            $xGrid, $yGrid, $winzoneObj, $number, $posn, $canvasObj, $canvasObj2,
            @colourList, @sortedList,
        );

        # Check for improper arguments
        if (! defined $xPos || ! defined $yPos || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->winzones1Tab_getMouseClick',
                @_,
            );
        }

        # Convert $xPos and $yPos - coordinates in the drawing canvas - into coordinates on the grid
        $xGrid = int( ($xPos - $self->borderX) / $self->cellWidth);
        $yGrid = int( ($yPos - $self->borderY) / $self->cellHeight);

        # Import a list of 32 colours in roughly rainbow order
        @colourList = $axmud::CLIENT->constRainbowColourList;

        # Ignore clicks that are outside the grid (e.g. the lines at the bottom right will give the
        #   values of $xGrid/$yGrid = 60 - we must ignore them)
        if (
            $xPos >= $self->borderX && $yPos >= $self->borderY
            && $xGrid >= 0 && $xGrid < $self->gridSize
            && $yGrid >= 0 && $yGrid < $self->gridSize
        ) {
            # See if this particular grid block is occupied; if so, display the number of the
            #   winzone that occupies it
            $winzoneObj = $self->editObj->findWinzone($xGrid, $yGrid, 1, 1);
            if ($winzoneObj) {

                # Refresh the grid, and return to mouse step 1
                $self->winzones1Tab_resetMouseStep();

                # Display the number and colour of the clicked zone in one of the entry boxes
                $number = $winzoneObj->number;

                $self->entryCurrentZone->set_text($number);
                $self->entryCurrentTable->set_text(
                    $axmud::CLIENT->ivShow('customTableHash', $winzoneObj->packageName),
                );

                # Winzones can be deleted, leaving a list of winzones numbered something like
                #   (0, 1, 2, 4)
                # Find the position in the list of the selected winzones, so we can assign the
                #   correct colour to it
                @sortedList = sort {$a <=> $b} ($self->editObj->ivKeys('zoneHash'));
                OUTER: for (my $count = 0; $count < scalar @sortedList; $count++) {

                    if ($sortedList[$count] == $number) {

                        $posn = $count;
                        last OUTER;
                    }
                }

                # Emergency fallback
                if (! defined $posn) {

                    $posn = 0;
                }

                # There are 32 colours in GA::Client->constRainbowColourList, after which we go back
                #   to the beginning of the list, and continue using colours. If $posn is more
                #   than 31, work out which colour is being used
                if ($posn >= (scalar @colourList)) {

                    $posn = $posn % (scalar @colourList);
                }

                # Set the colour displayed in the simple canvas
                $canvasObj = $self->fillSimpleCanvas(
                    $self->simpleCanvasWidget,
                    $self->simpleCanvasObj,
                    $colourList[$posn],
                    $self->simpleCanvasSize,
                    $self->simpleCanvasSize,
                );

                $self->ivPoke('simpleCanvasObj', $canvasObj);

                # Display the grid coordinates of the clicked block
                $self->entryStartX->set_text($winzoneObj->left);
                $self->entryStartY->set_text($winzoneObj->top);
                $self->entryStopX->set_text($winzoneObj->right);
                $self->entryStopY->set_text($winzoneObj->bottom);

                # Allow the 'Edit'/'Delete' buttons to be clicked now
                $self->editButton->set_sensitive(TRUE);
                $self->deleteButton->set_sensitive(TRUE);

                return 1;

            } elsif ($self->mouseStep == 1) {

                if ($self->clickMode) {

                    # 5-block clicks. Adjust $xGrid and $yGrid to the top-left corner of a group of
                    #   5x5 blocks
                    $xGrid = (int ($xGrid / 5) * 5);
                    $yGrid = (int ($yGrid / 5) * 5);
                }

                # Freeze these coordinates as the top-left corner of the new zone
                $self->ivPoke('startX', $xGrid);
                $self->ivPoke('startY', $yGrid);

                $self->entryStartX->set_text($xGrid);
                $self->entryStartY->set_text($yGrid);

                if (! $self->clickMode) {

                    # Mark the gridblock pink, as a visual aid
                    $canvasObj = $self->winzones1Tab_fillBlock(
                        '#FFC8CB',
                        $xGrid,
                        $yGrid,
                        TRUE,               # Don't add this to $self->drawnObjList
                    );

                } else {

                    # Mark a 5x5 group of gridblocks pink
                    $canvasObj = $self->winzones1Tab_fillZone(
                        '#FFC8CB',
                        $self->startX,
                        $self->startY,
                        $self->startX + 4,
                        $self->startY + 4,
                        TRUE,               # Don't add this to $self->drawnObjList
                    );
                }

                $self->ivPoke('selectCanvasObj', $canvasObj);

                # If the number of a zone that's been clicked on is displayed, remove it
                $self->entryCurrentZone->set_text('');
                $self->entryCurrentTable->set_text('');

                # Reset the colour displayed in the simple canvas
                $canvasObj2 = $self->fillSimpleCanvas(
                    $self->simpleCanvasWidget,
                    $self->simpleCanvasObj,
                    '#FFFFFF',
                    $self->simpleCanvasSize,
                    $self->simpleCanvasSize,
                );

                $self->ivPoke('simpleCanvasObj', $canvasObj2);

                # Desensitise the 'Edit'/'Delete' buttons
                $self->editButton->set_sensitive(FALSE);
                $self->deleteButton->set_sensitive(FALSE);

                # Move on to the next step
                $self->ivPoke('mouseStep', 2);

            } elsif ($self->mouseStep == 2) {

                my ($newStartX, $newStartY, $newStopX, $newStopY);

                if ($self->clickMode) {

                    # 5-block clicks. Adjust $xGrid and $yGrid to the top-left corner of a group of
                    #   5x5 blocks
                    $xGrid = (int ($xGrid / 5) * 5);
                    $yGrid = (int ($yGrid / 5) * 5);
                }

                # We may have to invert the two points, so that one is top-left, and the other is
                #   top-right

                # A....
                # ....B
                if ($xGrid >= $self->startX && $yGrid >= $self->startY) {

                    # (No inversion needed)
                    $newStartX = $self->startX;
                    $newStartY = $self->startY;
                    $newStopX = $xGrid;
                    $newStopY = $yGrid;

                # ....A
                # B....
                } elsif ($xGrid < $self->startX && $yGrid >= $self->startY) {

                    $newStartX = $xGrid;
                    $newStartY = $self->startY;
                    $newStopX = $self->startX;
                    $newStopY = $yGrid;

                # ....B
                # A....
                } elsif ($xGrid >= $self->startX && $yGrid < $self->startY) {

                    $newStartX = $self->startX;
                    $newStartY = $yGrid;
                    $newStopX = $xGrid;
                    $newStopY = $self->startY;

                # B....
                # ....A
                } else {

                    $newStartX = $xGrid;
                    $newStartY = $yGrid;
                    $newStopX = $self->startX;
                    $newStopY = $self->startY;
                }

                # If we are doing blocks of 5, the bottom-right click (which might be A or B, but
                #   which is now $newStopX and $newStopY) is at the top-left of a block of 5.
                #   Adjust the coordinates so they are at the bottom-right of the block of 5.
                if ($self->clickMode) {

                    $newStopX += 4;
                    $newStopY += 4;
                }

                # Freeze these coordinates as the top-left and bottom-right corners of the zone
                $self->ivPoke('startX', $newStartX);
                $self->ivPoke('startY', $newStartY);
                $self->ivPoke('stopX', $newStopX);
                $self->ivPoke('stopY', $newStopY);

                $self->entryStartX->set_text($newStartX);
                $self->entryStartY->set_text($newStartY);
                $self->entryStopX->set_text($newStopX);
                $self->entryStopY->set_text($newStopY);

                # Paint the potentional zone pink, as a visual aid
                $canvasObj = $self->winzones1Tab_fillZone(
                    '#FFC8CB',
                    $self->startX,
                    $self->startY,
                    $self->stopX,
                    $self->stopY,
                    TRUE,               # Don't add this to $self->drawnObjList
                );

                $self->ivPoke('selectCanvasObj', $canvasObj);

                # The 'Add winzone'/'Cancel' buttons are now sensitised, as is the combo
                $self->addZoneCombo->set_sensitive(TRUE);
                $self->addZoneButton->set_sensitive(TRUE);
                $self->cancelButton->set_sensitive(TRUE);

                # Move on to the next step
                $self->ivPoke('mouseStep', 3);

            } elsif ($self->mouseStep == 3) {

                # Refresh the grid, and return to mouse step 1
                $self->winzones1Tab_resetMouseStep();
            }
        }

        return 1;
    }

    sub winzones1Tab_getMouseMotion {

        # Called by ->signal_connect in $self->addDrawingCanvas (inherited from GA::Generic::Win),
        #   whenever the user moves the mouse pointer over the drawing canvas
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $xPos, $yPos    - The current coordiantes of the mouse over the drawing canvas
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $xPos, $yPos, $check) = @_;

        # Local variables
        my ($xGrid, $yGrid);

        # Check for improper arguments
        if (! defined $xPos || ! defined $yPos || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->winzones1Tab_getMouseMotion',
                @_,
            );
        }

        # Convert $xPos and $yPos - coordinates in the drawing canvas - into coordinates on the grid
        $xGrid = int( ($xPos - $self->borderX) / $self->cellWidth);
        $yGrid = int( ($yPos - $self->borderY) / $self->cellHeight);

        # Ignore positions that are outside the grid
        if ($xGrid >= 0 && $xGrid <= $self->gridSize && $yGrid >= 0 && $yGrid <= $self->gridSize) {

            # Update the entry boxes, or not, depending on which mouse step we're at
            if ($self->mouseStep == 1) {

                # Update all four entry boxes
                $self->entryStartX->set_text($xGrid);
                $self->entryStartY->set_text($yGrid);
                $self->entryStopX->set_text($xGrid);
                $self->entryStopY->set_text($yGrid);

            } elsif ($self->mouseStep == 2) {

                # The first two entry boxes are fixed
                $self->entryStopX->set_text($xGrid);
                $self->entryStopY->set_text($yGrid);
            }
        }

        return 1;
    }

    sub winzones1Tab_refreshGrid {

        # Called by ->winzones1Tab
        # Draws (or re-draws) all existing winzones
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @colourList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->winzones1Tab_refreshGrid',
                @_,
            );
        }

        # Remove any existing winzones
        foreach my $canvasObj ($self->drawnObjList) {

            $canvasObj->remove();
        }

        # Import a list of 32 colours in roughly rainbow order
        @colourList = $axmud::CLIENT->constRainbowColourList;
        # Draw each winzone in turn, using a different colour for each. If we run out of colours,
        #   start again from the first colour
        foreach my $winzoneNum (sort {$a <=> $b} ($self->editObj->ivKeys('zoneHash'))) {

            my ($winzoneObj, $colour);

            $winzoneObj = $self->editObj->ivShow('zoneHash', $winzoneNum);

            $colour = shift @colourList;
            if (! $colour) {

                # All of the colours have been used - so re-import the list, and start taking
                #   colours from the beginning again
                @colourList = $axmud::CLIENT->constRainbowColourList;
                $colour = shift @colourList;
            }

            $self->winzones1Tab_fillZone(
                $colour,
                $winzoneObj->left,
                $winzoneObj->top,
                $winzoneObj->right,
                $winzoneObj->bottom,
            );
        }

        # Operation complete
        return 1;
    }

    sub winzones1Tab_fillBlock {

        # Called by $self->winzones1Tab_getMouseClick
        # Fills in a single block of the grid in a single colour
        #
        # Expected arguments
        #   $colour         - The colour (an RGB tag, e.g. '#ABCDEF')
        #   $xPos, $yPos    - The coordinates of the gridblock to be filled in
        #
        # Optional arguments
        #   $tempFlag       - If TRUE, it's a temporary (pink) area of the grid that should not be
        #                       added to $self->drawnObjList. FALSE (or 'undef') otherwise
        #
        # Return values
        #   'undef' on improper arguments
        #   The drawn canvas object otherwise

        my ($self, $colour, $xPos, $yPos, $tempFlag, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $colour || ! defined $xPos || ! defined $yPos || ! defined $colour
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->winzones1Tab_fillBlock', @_);
        }

        # (Drawing any coloured area displaces any selection that has been made)
        if ($self->selectCanvasObj) {

            $self->selectCanvasObj->remove();
            $self->ivUndef('selectCanvasObj');
        }

        # Fill the gridblock at grid coordinates $xPos, $yPos
        my $canvasObj = GooCanvas2::CanvasRect->new(
            'parent' => $self->canvasWidget->get_root_item(),
            x => $self->borderX + ($xPos * $self->cellWidth),
            y => $self->borderY + ($yPos * $self->cellHeight),
            width => $self->cellWidth,
            height => $self->cellHeight,
            'line-width' => 1,
            'stroke-color' => $colour,
            'fill-color' => $colour,
        );

        $canvasObj->lower($self->gridCanvasObj);
        if (! $tempFlag) {

            $self->ivPush('drawnObjList', $canvasObj);
        }

        return $canvasObj;
    }

    sub winzones1Tab_fillZone {

        # Called by $self->winzones1Tab_getMouseClick and ->winzones1Tab_refreshGrid
        # Fills in an area of the grid, 5x5 blocks, in a single colour
        #
        # Expected arguments
        #   $colour         - The colour (an RGB tag, e.g. '#ABCDEF')
        #   $startX, $startY, $stopX, $stopY
        #                   - The area of the grid to fill in
        #
        # Optional arguments
        #   $tempFlag       - If TRUE, it's a temporary (pink) area of the grid that should not be
        #                       added to $self->drawnObjList. FALSE (or 'undef') otherwise
        #
        # Return values
        #   'undef' on improper arguments or if $colour is invalid
        #   The drawn canvas object otherwise

        my ($self, $colour, $startX, $startY, $stopX, $stopY, $tempFlag, $check) = @_;

        # Local variables
        my ($x, $y, $width, $height);

        # Check for improper arguments
        if (
            ! defined $colour || ! defined $startX || ! defined $startY || ! defined $stopX
            || ! defined $stopY || ! defined $colour || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->winzones1Tab_fillZone', @_);
        }

        # (Drawing any coloured area displaces any selection that has been made)
        if ($self->selectCanvasObj) {

            $self->selectCanvasObj->remove();
            $self->ivUndef('selectCanvasObj');
        }

        # For reasons unknown, the coloured zone seeps outside the top and left boundaries of the
        #   grid. Adjust the zone's size and position to prevent that
        $x = $self->borderX + ($startX * $self->cellWidth);
        $y = $self->borderY + ($startY * $self->cellHeight);
        $width = ($self->cellWidth * ($stopX - $startX + 1));
        $height = ($self->cellHeight * ($stopY - $startY + 1));
        if ($x <= $self->borderX) {

            $x++;
            $width--;
        }

        if ($y <= $self->borderY) {

            $y++;
            $height--;
        }

        my $canvasObj = GooCanvas2::CanvasRect->new(
            'parent' => $self->canvasWidget->get_root_item(),
            x => $x,
            y => $y,
            width => $width,
            height => $height,
            'line-width' => 1,
            'stroke-color' => $colour,
            'fill-color' => $colour,
        );

        $canvasObj->lower($self->gridCanvasObj);
        if (! $tempFlag) {

            $self->ivPush('drawnObjList', $canvasObj);
        }

        return $canvasObj;
    }

    sub winzones2Tab {

        # Winzones2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->winzones2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Zone size settings'],
        );

        # Zone size settings
        $self->addLabel($grid, '<b>Zone size settings</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            "<i>Standard size for adding new table objects to an existing window, if a default size"
            . " is required. Use a size\nthat will fill a 60x60 grid without leaving gaps, e.g."
            . " 10x10, 15x15, 30x15, 20x20, 12x60, 60x5 etc</i>",
            2, 12, 1, 2);

        $self->addLabel($grid, 'Zone width (1-60)',
            2, 4, 2, 3);
        $self->addEntryWithIcon($grid, 'zoneWidth', 'int', 1, 60,
            4, 6, 2, 3);
        $self->addLabel($grid, 'Zone height (1-60)',
            7, 9, 2, 3);
        $self->addEntryWithIcon($grid, 'zoneHeight', 'int', 1, 60,
            9, 12, 2, 3);

        # Tab complete
        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    # (Non-standard IVs for this 'edit' window)
    sub borderX
        { $_[0]->{borderX} }
    sub borderY
        { $_[0]->{borderY} }
    sub gridSize
        { $_[0]->{gridSize} }
    sub cellWidth
        { $_[0]->{cellWidth} }
    sub cellHeight
        { $_[0]->{cellHeight} }
    sub clickMode
        { $_[0]->{clickMode} }

    sub canvasWidget
        { $_[0]->{canvasWidget} }
    sub bgCanvasObj
        { $_[0]->{bgCanvasObj} }
    sub gridCanvasObj
        { $_[0]->{gridCanvasObj} }
    sub gridCanvasObj2
        { $_[0]->{gridCanvasObj2} }
    sub selectCanvasObj
        { $_[0]->{selectCanvasObj} }
    sub drawnObjList
        { my $self = shift; return @{$self->{drawnObjList}}; }

    sub entryStartX
        { $_[0]->{entryStartX} }
    sub entryStartY
        { $_[0]->{entryStartY} }
    sub entryStopX
        { $_[0]->{entryStopX} }
    sub entryStopY
        { $_[0]->{entryStopY} }
    sub addZoneCombo
        { $_[0]->{addZoneCombo} }
    sub addZoneButton
        { $_[0]->{addZoneButton} }
    sub cancelButton
        { $_[0]->{cancelButton} }
    sub mouseStep
        { $_[0]->{mouseStep} }
    sub startX
        { $_[0]->{startX} }
    sub startY
        { $_[0]->{startY} }
    sub stopX
        { $_[0]->{stopX} }
    sub stopY
        { $_[0]->{stopY} }

    sub entryCurrentZone
        { $_[0]->{entryCurrentZone} }
    sub entryCurrentTable
        { $_[0]->{entryCurrentTable} }
    sub frame
        { $_[0]->{frame} }
    sub simpleCanvasWidget
        { $_[0]->{simpleCanvasWidget} }
    sub simpleCanvasObj
        { $_[0]->{simpleCanvasObj} }
    sub simpleCanvasSize
        { $_[0]->{simpleCanvasSize} }
    sub editButton
        { $_[0]->{editButton} }
    sub deleteButton
        { $_[0]->{deleteButton} }
}

{ package Games::Axmud::EditWin::Winzone;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Winzone')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->numberTab();

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->initTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub numberTab {

        # Number tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (@columnList, @comboList);

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->numberTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Number',
            ['Winzone settings'],
        );

        # Left column
        $self->addLabel($grid, '<b>Winzone settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, 'Winzone number',
            1, 3, 1, 2);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 1, 2);

        $self->addLabel($grid, 'Contains table object',
            1, 3, 2, 3);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 2, 3);
        $entry->set_text($axmud::CLIENT->ivShow('customTableHash', $self->editObj->packageName));

        $self->addLabel($grid, 'Package name',
            1, 3, 3, 4);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 3, 4);
        $entry2->set_text($self->editObj->packageName);

        $self->addLabel($grid, 'Optional winzone name',
            1, 3, 4, 5);
        $self->addEntryWithButton($grid, 'objName', TRUE,
            3, 6, 4, 5);

        # Right column
        $self->addLabel($grid, 'Parent winmap',
            7, 9, 1, 2);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            9, 12, 1, 2);
        $entry3->set_text($self->editObj->winmapObj->name);

        $self->addLabel($grid, '<i>Winzone coordinates in winmap</i>',
            7, 12, 2, 3);
        $self->addLabel($grid, 'Top-left X (blocks)',
            7, 9, 3, 4);
        $self->addEntry($grid, 'left', FALSE,
            9, 12, 3, 4);
        $self->addLabel($grid, 'Top-left Y',
            7, 9, 4, 5);
        $self->addEntry($grid, 'top', FALSE,
            9, 12, 4, 5);
        $self->addLabel($grid, 'Bottom-right X',
            7, 9, 5, 6);
        $self->addEntry($grid, 'right', FALSE,
            9, 12, 5, 6);
        $self->addLabel($grid, 'Bottom-right Y',
            7, 9, 6, 7);
        $self->addEntry($grid, 'bottom', FALSE,
            9, 12, 6, 7);
        $self->addLabel($grid, 'Width (blocks)',
            7, 9, 7, 8);
        $self->addEntry($grid, 'width', FALSE,
            9, 12, 7, 8);
        $self->addLabel($grid, 'Height',
            7, 9, 8, 9);
        $self->addEntry($grid, 'height', FALSE,
            9, 12, 8, 9);

        # Tab complete
        return 1;
    }

    sub initTab {

        # Init tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $package, $tempObj,
            @columnList, @comboList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->initTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Initialisation',
            ['Initialisation settings'],
        );

        # Create a temporary table object so we can access its IVs
        $package = $self->editObj->packageName;
        if ($package) {

            $tempObj = $package->new(-1, 'temp', 'temp', 'temp');
        }

        # Initialisation settings
        $self->addLabel($grid, '<b>Initialisation settings</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of initialisation settings for the table object, overriding its default'
            . ' its settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 9);

        # Initialise the simple list
        $self->initTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets (if the temporary table object was created, which might not be the
        #   case if a plugin wasn't enabled)
        if ($tempObj) {

            $self->addLabel($grid, 'Initialisation key/value',
                1, 2, 9, 10);
            @comboList = (sort {lc($a) cmp lc($b)} ($tempObj->ivKeys('initHash')));
            my $comboBox = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,              # no 'undef' value allowed
                2, 6, 9, 10);

            my $entry = $self->addEntry($grid, undef, TRUE,
                6, 10, 9, 10);

            my $button = $self->addButton(
                $grid,
                'Use value',
                'Use the specified initialisation key with the value above',
                undef,
                10, 12, 9, 10);

            my $button2 = $self->addButton(
                $grid,
                'Use the default setting instead',
                'Don\'t use the specified initialisation setting',
                undef,
                1, 6, 10, 11);

            my $button3 = $self->addButton(
                $grid,
                'Use \'undef\'',
                'Use the specified initialisation key with an \'undef\' value',
                undef,
                6, 8, 10, 11);

            my $button4 = $self->addButton(
                $grid,
                'Use TRUE',
                'Use the specified initialisation key with a TRUE value',
                undef,
                8, 9, 10, 11);

            my $button5 = $self->addButton(
                $grid,
                'Use FALSE',
                'Use the specified initialisation key with a FALSE value',
                undef,
                10, 12, 10, 11);

            my $button6 = $self->addButton(
                $grid,
                'Reset list',
                'Reset the initialisation settings',
                undef,
                10, 12, 11, 12);

            # Sensitise/desensitise widgets
            $self->initTab_updateWidgets(
                $comboBox->get_active_text(),
                $entry, $button, $button2, $button3, $button4, $button5,
            );

            # ->signal_connects
            $comboBox->signal_connect('changed' => sub {

                $self->initTab_updateWidgets(
                    $comboBox->get_active_text(),
                    $entry, $button, $button2, $button3, $button4, $button5,
                );
            });

            # 'Use value'
            $button->signal_connect('clicked' => sub {

                my ($key, $value);

                $key = $comboBox->get_active_text();
                $value = $entry->get_text();

                if (defined $key) {

                    $self->modifyEditHash_hashIV('initHash', $key, $value);

                    # Refresh the simple list
                    $self->initTab_refreshList($slWidget, scalar (@columnList / 2));
                }
            });

            # 'Use the default setting instead'
            $button2->signal_connect('clicked' => sub {

                my $key = $comboBox->get_active_text();
                if (defined $key) {

                    $self->modifyEditHash_hashIV('initHash', $key, undef, TRUE);

                    # Refresh the simple list
                    $self->initTab_refreshList($slWidget, scalar (@columnList / 2));
                }
            });

            # 'Use undef'
            $button3->signal_connect('clicked' => sub {

                my $key = $comboBox->get_active_text();
                if (defined $key) {

                    $self->modifyEditHash_hashIV('initHash', $key, undef);

                    # Refresh the simple list
                    $self->initTab_refreshList($slWidget, scalar (@columnList / 2));
                }
            });

            # 'Use TRUE'
            $button4->signal_connect('clicked' => sub {

                my $key = $comboBox->get_active_text();
                if (defined $key) {

                    $self->modifyEditHash_hashIV('initHash', $key, TRUE);

                    # Refresh the simple list
                    $self->initTab_refreshList($slWidget, scalar (@columnList / 2));
                }
            });

            # 'Use FALSE'
            $button5->signal_connect('clicked' => sub {

                my $key = $comboBox->get_active_text();
                if (defined $key) {

                    $self->modifyEditHash_hashIV('initHash', $key, FALSE);

                    # Refresh the simple list
                    $self->initTab_refreshList($slWidget, scalar (@columnList / 2));
                }
            });

            # 'Reset list'
            $button6->signal_connect('clicked' => sub {

                $self->ivDelete('editHash', 'initHash');

                # Refresh the simple list
                $self->initTab_refreshList($slWidget, scalar (@columnList / 2));
            });
        }

        # Tab complete
        return 1;
    }

    sub initTab_refreshList {

        # Resets the simple list displayed by $self->file2Tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Obj::SimpleList
        #   $columns        - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->initTab_refreshList', @_);
        }

        # Import the hash
        %ivHash = $self->getEditHash_hashIV('initHash');

        # Compile the simple list data
        foreach my $key (sort {lc($a) cmp lc($b)} (keys %ivHash)) {

            my $value = $ivHash{$key};

            if ($key =~ m/_flag$/) {

                if (! $value) {
                    $value = 'FALSE';
                } else {
                    $value = 'TRUE';
                }

            } elsif (! defined $value) {

                $value = '<undef>';
            }

            push (@dataList,
                $key,
                $value,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub initTab_updateWidgets {

        # Called by $self->stripsTab
        # Sensitises/desensitises widgets depending on which item is the active one in the combobox
        #
        # Expected arguments
        #   $key        - The active item in the combobox
        #   $entry, $button, $button2, $button3, $button4, $button5
        #               - The widgets to update
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $key, $entry, $button, $button2, $button3, $button4, $button5, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $key || ! defined $entry || ! defined $button || ! defined $button2
            || ! defined $button3 || ! defined $button4 || ! defined $button5 || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->initTab_updateWidgets',
                @_,
            );
        }

        if ($key =~ m/_flag$/) {

            $self->sensitiseWidgets($button4, $button5);
            $self->desensitiseWidgets($entry, $button, $button2, $button3);

        } else {

            $self->sensitiseWidgets($entry, $button, $button2, $button3);
            $self->desensitiseWidgets($button4, $button5);
        }

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    # (Non-standard IVs for this 'edit' window)
}

{ package Games::Axmud::EditWin::Workspace;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Workspace')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

        my ($self, $hBox, $tooltips, $check) = @_;

        # Local variables
        my @emptyList;

        # Check for improper arguments
        if (! defined $hBox || defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableButtons', @_);
            return @emptyList;
        }

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->numberTab();

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->gridsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub numberTab {

        # Number tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $ownerObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->numberTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Number',
            ['Workspace object'],
        );

        # Left column
        $self->addLabel($grid, '<b>Workspace object</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Settings for this workspace object</i>',
            1, 6, 1, 2);
        $self->addLabel($grid, 'Object number',
            1, 3, 2, 3);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'System workspace number',
            1, 3, 3, 4);
        my $entry = $self->addEntry($grid, 'systemNum', FALSE,
            3, 6, 3, 4);

        $self->addLabel($grid, 'Current width (pixels)',
            1, 3, 4, 5);
        $self->addEntry($grid, 'currentWidth', FALSE,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Current height',
            1, 3, 5, 6);
        $self->addEntry($grid, 'currentHeight', FALSE,
            3, 6, 5, 6);
        $self->addLabel($grid, 'Default zonemap',
            1, 3, 6, 7);
        $self->addEntry($grid, 'defaultZonemap', FALSE,
            3, 6, 6, 7);

        # Right column
        $self->addLabel($grid, '<i>Panel sizes for this workspace (if detected)</i>',
            7, 12, 1, 2);
        $self->addLabel($grid, 'Left',
            7, 9, 2, 3);
        $self->addEntry($grid, 'panelLeftSize', FALSE,
            9, 12, 2, 3);
        $self->addLabel($grid, 'Right',
            7, 9, 3, 4);
        $self->addEntry($grid, 'panelRightSize', FALSE,
            9, 12, 3, 4);
        $self->addLabel($grid, 'Top',
            7, 9, 4, 5);
        $self->addEntry($grid, 'panelTopSize', FALSE,
            9, 12, 4, 5);
        $self->addLabel($grid, 'Bottom',
            7, 9, 5, 6);
        $self->addEntry($grid, 'panelBottomSize', FALSE,
            9, 12, 5, 6);

        $self->addLabel($grid, '<i>Window controls sizes (if detected)</i>',
            7, 12, 6, 7);
        $self->addLabel($grid, 'Left',
            7, 9, 7, 8);
        $self->addEntry($grid, 'controlsLeftSize', FALSE,
            9, 12, 7, 8);
        $self->addLabel($grid, 'Right',
            7, 9, 8, 9);
        $self->addEntry($grid, 'controlsRightSize', FALSE,
            9, 12, 8, 9);
        $self->addLabel($grid, 'Top',
            7, 9, 9, 10);
        $self->addEntry($grid, 'controlsTopSize', FALSE,
            9, 12, 9, 10);
        $self->addLabel($grid, 'Bottom',
            7, 9, 10, 11);
        $self->addEntry($grid, 'controlsBottomSize', FALSE,
            9, 12, 10, 11);

        # Tab complete
        return 1;
    }

    sub gridsTab {

        # Grids tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $existString,
            @columnList, @comboList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->gridsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Grids',
            ['Workspace grids'],
        );

        # Workspace grids
        $self->addLabel($grid, '<b>Workspace grids</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of workspace grids on this workspace</i>',
            1, 6, 1, 2);
        $self->addCheckButton($grid, 'Grids enabled on this workspace', 'gridEnableFlag', FALSE,
            7, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Grid num', 'int',
            'Session', 'text',
            'Zonemap', 'text',
            'Layers', 'int',
            'Max', 'int',
            'No. zones', 'int',
            'No. windows', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->gridsTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Zonemap',
            1, 3, 10, 11);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('zonemapHash'));
        $existString = '(use existing zonemap)';
        my $combo = $self->addComboBox($grid, undef, \@comboList, $existString,
            TRUE,               # No 'undef' value used
            3, 5, 10, 11);

        my $button = $self->addButton(
            $grid,
            'Reset',
            'Reset selected workspace grid using the specified default zonemap',
            undef,
            5, 7, 10, 11,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($number, $cmd, $zonemap);

            ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number) {

                $cmd = 'resetgrid ' . $number;

                $zonemap = $combo->get_active_text();
                if ($zonemap ne $existString) {

                    $cmd .= ' ' . $zonemap;
                }

                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);
            }

            # Refresh the list
            $self->gridsTab_refreshList($slWidget, scalar (@columnList / 2));
            # Update widgets
            $combo->set_active(0);
        });

        my $button2 = $self->addButton(
            $grid,
            'View...',
            'View settings for the selected workspace grid',
            undef,
            8, 10, 10, 11);
        $button2->signal_connect('clicked' => sub {

            my ($number, $obj);

            ($number) = $self->getSimpleListData($slWidget, 0);
            if (defined $number) {

                $obj = $axmud::CLIENT->desktopObj->ivShow('gridHash', $number);
                if ($obj) {

                    # Open an 'edit' window to edit the workspace grid
                    $self->createFreeWin(
                        'Games::Axmud::EditWin::WorkspaceGrid',
                        $self,
                        $self->session,
                        'Edit workspace grid #' . $number,
                        $obj,
                        FALSE,                          # Not temporary
                    );
                }
            }

            # Refresh the list
            $self->gridsTab_refreshList($slWidget, scalar (@columnList / 2));
            # Update widgets
            $combo->set_active(0);
        });

        my $button3 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of workspace grids',
            undef,
            10, 12, 10, 11);
        $button3->signal_connect('clicked' => sub {

            # Refresh the list
            $self->gridsTab_refreshList($slWidget, scalar (@columnList / 2));
            # Update widgets
            $combo->set_active(0);
        });

        # Tab complete
        return 1;
    }

    sub gridsTab_refreshList {

        # Called by $self->gridTab to refresh the first GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->gridsTab_refreshList', @_);
        }

        # Compile the simple list data
        foreach my $obj (
            sort {$a->number <=> $b->number} ($self->editObj->ivValues('gridHash'))
        ) {
            my $string;

            if ($obj->owner) {
                $string = $obj->owner->number;
            } else {
                $string = '(shared)';
            }

            push (@dataList,
                $obj->number,
                $string,
                $obj->zonemap,
                $obj->currentLayer,
                $obj->maxLayers,
                $obj->ivPairs('zoneHash'),
                $obj->ivPairs('gridWinHash'),
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::WorkspaceGrid;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::WorkspaceGrid')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

        my ($self, $hBox, $check) = @_;

        # Local variables
        my @emptyList;

        # Check for improper arguments
        if (! defined $hBox || defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableButtons', @_);
            return @emptyList;
        }

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->numberTab();

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->zonesTab();
        $self->windowsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub numberTab {

        # Number tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $ownerObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->numberTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Number',
            ['Workspace grid'],
        );

        # Left column
        $self->addLabel($grid, '<b>Workspace grid</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Settings for this workspace grid</i>',
            1, 6, 1, 2);
        $self->addLabel($grid, 'Grid number',
            1, 3, 2, 3);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($grid, 'On workspace',
            1, 3, 3, 4);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 3, 4);
        $entry->set_text($self->editObj->workspaceObj->number);
        $self->addLabel($grid, 'Controlling session',
            1, 3, 4, 5);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 4, 5);
        if (! $self->editObj->owner) {
            $entry2->set_text('(shared)');
        } else {
            $entry2->set_text($self->editObj->owner->number);
        }
        $self->addLabel($grid, 'Zonemap',
            1, 3, 5, 6);
        $self->addEntry($grid, 'zonemap', FALSE,
            3, 6, 5, 6);

        $self->addLabel($grid, '<i>Layers</i>',
            1, 6, 6, 7);
        $self->addLabel($grid, 'Current layer',
            1, 3, 7, 8);
        $self->addEntry($grid, 'currentLayer', FALSE,
            3, 6, 7, 8);
        $self->addLabel($grid, 'Max layers',
            1, 3, 8, 9);
        $self->addEntry($grid, 'maxLayers', FALSE,
            3, 6, 8, 9);

        # Right column
        $self->addLabel($grid, '<i>Size of grid (in gridblocks)</i>',
            7, 12, 1, 2);
        $self->addLabel($grid, 'Width',
            7, 9, 2, 3);
        $self->addEntry($grid, 'widthBlocks', FALSE,
            9, 12, 2, 3);
        $self->addLabel($grid, 'Height',
            7, 9, 3, 4);
        $self->addEntry($grid, 'heightBlocks', FALSE,
            9, 12, 3, 4);

        $self->addLabel($grid, '<i>Position of grid on workspace (pixels)</i>',
            7, 12, 4, 5);
        $self->addLabel($grid, 'X',
            7, 9, 5, 6);
        $self->addEntry($grid, 'xPosPixels', FALSE,
            9, 12, 5, 6);
        $self->addLabel($grid, 'Y',
            7, 9, 6, 7);
        $self->addEntry($grid, 'yPosPixels', FALSE,
            9, 12, 6, 7);

        # Tab complete
        return 1;
    }

    sub zonesTab {

        # Zones tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->zonesTab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Zones');

        # Zones
        $self->addLabel($grid, '<b>Zones</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of zones on this workspace grid (each with room for one or more windows)</i>',
            1, 6, 1, 2);

        # Add a simple list
        @columnList = (
            'Num', 'int',
            'X', 'int',
            'Y', 'int',
            'Wid', 'int',
            'Hei', 'int',
            'Mult layers', 'bool',
            'No. wins', 'int',
            'Max wins', 'int',
            'Reserved (type:name)', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->zonesTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add a button
        my $button = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of zones for this workspace grid', undef,
            10, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            # Refresh the list
            $self->zonesTab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub zonesTab_refreshList {

        # Called by $self->zonesTab to refresh the first GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@typeList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->zonesTab_refreshList', @_);
        }

        # Compile a list of 'grid' window types
        @typeList = ('main', 'map', 'protocol', 'fixed', 'custom', 'external');

        # Compile the simple list data
        foreach my $obj (
            sort {$a->number <=> $b->number} ($self->editObj->ivValues('zoneHash'))
        ) {
            my $string;

            $string = '';
            foreach my $type (@typeList) {

                if ($obj->ivExists('reservedHash', $type)) {

                    if ($string) {

                        $string .= ' ';
                    }

                    $string .= $type . ':' . $obj->ivShow('reservedHash', $type);
                }
            }

            push (@dataList,
                $obj->number,
                $obj->xPosBlocks,
                $obj->yPosBlocks,
                $obj->widthBlocks,
                $obj->heightBlocks,
                $obj->multipleLayerFlag,
                $obj->ivPairs('areaHash'),
                $obj->areaMax,
                $string,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub windowsTab {

        # Windows tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->windowsTab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Windows');

        # Windows
        $self->addLabel($grid, '<b>Windows</b>',
            0, 12, 0, 1);
        $self->addLabel($grid,
            '<i>List of \'grid\' windows arranged on this workspace grid</i>',
            1, 6, 1, 2);

        # Add a simple list
        @columnList = (
            'Window', 'int',
            'Area', 'int',
            'Zone', 'int',
            'Layer', 'int',
            'Left (blocks)', 'int',
            'Right', 'int',
            'Top', 'int',
            'Bottom', 'int',
            'Wid', 'int',
            'Hei', 'int',
            'X (pixels)', 'int',
            'Y', 'int',
            'Wid', 'int',
            'Hei', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->windowsTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add buttons
        my $button = $self->addButton(
            $grid,
            'View...',
            'View settings for the selected \'grid\' window', undef,
            8, 10, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($num, $obj);

            ($num) = $self->getSimpleListData($slWidget, 0);
            if (defined $num) {

                $obj = $axmud::CLIENT->desktopObj->ivShow('gridWinHash', $num);
                if ($obj) {

                    # Open an 'edit' window for the 'grid' window
                    $self->createFreeWin(
                        'Games::Axmud::EditWin::Window',
                        $self,
                        $self->session,
                        'Edit \'grid\' window #' . $obj->number,
                        $obj,
                        FALSE,                          # Not temporary
                    );
                }
            }

            # Refresh the list
            $self->windowsTab_refreshList($slWidget, scalar (@columnList / 2));
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of windows on this workspace grid', undef,
            10, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

            # Refresh the list
            $self->windowsTab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub windowsTab_refreshList {

        # Called by $self->windowsTab to refresh the first GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my @dataList;

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->windowsTab_refreshList', @_);
        }

        # Compile the simple list data
        foreach my $obj (
            sort {$a->number <=> $b->number} ($self->editObj->ivValues('gridWinHash'))
        ) {
            push (@dataList,
                $obj->number,
                $obj->areaObj->number,
                $obj->areaObj->zoneObj->number,
                $obj->areaObj->layer,
                $obj->areaObj->leftBlocks,
                $obj->areaObj->topBlocks,
                $obj->areaObj->rightBlocks,
                $obj->areaObj->bottomBlocks,
                $obj->areaObj->widthBlocks,
                $obj->areaObj->heightBlocks,
                $obj->areaObj->xPosPixels,
                $obj->areaObj->yPosPixels,
                $obj->areaObj->widthPixels,
                $obj->areaObj->heightPixels,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::WorldModel;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::WorldModel')) {
            return undef;
        } else {
            return 1;
        }
    }

#   sub enableButtons {}        # Inherited from GA::Generic::ConfigWin

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->generalTab();

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->sizesTab();
        $self->modelTab();
        $self->settingsTab();
        $self->coloursTab();
        $self->stylesTab();
        $self->roomFlagsTab();
        $self->lightTab();

        return 1;
    }

    sub saveChanges {

        # Called by $self->buttonOK and $self->buttonSave (usually for 'edit' windows only, not
        #   'pref' windows)
        # Saves any changes made to data stored by the edit object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $blockWidth, $blockHeight, $roomWidth, $roomHeight, $failFlag, $newValue, $regionmapObj,
            @ivList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->saveChanges', @_);
        }

        if ($self->editHash) {

            # If new gridblock/room sizes have been specified, must check that the room is smaller
            #   than a gridblock
            if (
                $self->ivExists('editHash', 'defaultBlockWidthPixels')
                || $self->ivExists('editHash', 'defaultRoomWidthPixels')
            ) {
                $blockWidth = $self->getEditHash_scalarIV('defaultBlockWidthPixels');
                $roomWidth = $self->getEditHash_scalarIV('defaultRoomWidthPixels');

                if ($roomWidth >= $blockWidth) {

                    $failFlag = TRUE;
                }
            }

            if (! $failFlag) {

                if (
                    $self->ivExists('editHash', 'defaultBlockHeightPixels')
                    || $self->ivExists('editHash', 'defaultRoomHeightPixels')
                ) {
                    $blockHeight = $self->getEditHash_scalarIV('defaultBlockHeightPixels');
                    $roomHeight = $self->getEditHash_scalarIV('defaultRoomHeightPixels');

                    if ($roomHeight >= $blockHeight) {

                        $failFlag = TRUE;
                    }
                }
            }

            if ($failFlag) {

                # Ignore all changes to the room/block width/height
                $self->ivDelete('editHash', 'defaultBlockWidthPixels');
                $self->ivDelete('editHash', 'defaultRoomWidthPixels');
                $self->ivDelete('editHash', 'defaultBlockHeightPixels');
                $self->ivDelete('editHash', 'defaultRoomHeightPixels');
            }

            # Some widgets (especially those in $self->settingsXTabs) require a call to the world
            #   model object, when they are changed. Deal with them each in turn

            # (Automapper window components)
            @ivList = (
                'showMenuBarFlag', 'showToolbarFlag', 'showTreeViewFlag', 'showCanvasFlag',
            );

            foreach my $iv (@ivList) {

                if ($self->ivExists('editHash', $iv)) {

                    if ($self->ivShow('editHash', $iv) ne $self->editObj->$iv) {

                        $self->editObj->toggleWinComponents(
                            $iv,
                            $self->ivShow('editHash', $iv),
                        );
                    }

                    $self->ivDelete('editHash', $iv);
                }
            }

            # (GA::Obj::WorldModel flags)
            @ivList = (
                # ->settings1Tab
                'drawOrnamentsFlag', 'draw_ornaments', 'icon_draw_ornaments',
                'obscuredExitFlag', 'obscured_exits', 'icon_obscured_exits',
                'obscuredExitRedrawFlag', 'auto_redraw_obscured', 'icon_auto_redraw_obscured',
                'trackPosnFlag', 'track_current_room', 'icon_track_current_room',
                'preDrawAllowFlag', 'allow_pre_draw', undef,
                 # ->settings2Tab
                 # (none)
                # ->settings3Tab
                'matchTitleFlag', 'match_title', undef,
                'matchDescripFlag', 'match_descrip', undef,
                'matchExitFlag', 'match_exit', undef,
                'analyseDescripFlag', 'analyse_descrip', undef,
                'matchSourceFlag', 'match_source', undef,
                'matchVNumFlag', 'match_vnum', undef,
                'updateTitleFlag', 'update_title', undef,
                'updateDescripFlag', 'update_descrip', undef,
                'updateExitFlag', 'update_exit', undef,
                'updateSourceFlag', 'update_source', undef,
                'updateVNumFlag', 'update_vnum', undef,
                'updateRoomCmdFlag', 'update_room_cmd', undef,
                'updateOrnamentFlag', 'update_ornament', undef,
                 # ->settings4Tab
#               'autoCompareAllFlag', 'auto_compare_region', undef,         # Set below
                'autoRescueFlag', 'auto_rescue', undef,
                'autoRescueFirstFlag', 'auto_rescue_first', undef,
                'autoRescuePromptFlag', 'auto_rescue_prompt', undef,
                'autoRescueNoMoveFlag', 'auto_rescue_no_move', undef,
                'autoRescueVisitsFlag', 'auto_rescue_visits', undef,
                'autoRescueForceFlag', 'auto_rescue_foce', undef,
                 # ->settings5Tab
                'allowModelScriptFlag', 'allow_model_scripts', undef,
                'allowRoomScriptFlag', 'allow_room_scripts', undef,
                 # ->settings6Tab
                # (none)
                # ->settings7Tab
                'assistedMovesFlag', 'allow_assisted_moves', undef,
                'assistedBreakFlag', 'break_before_move', undef,
                'assistedPickFlag', 'pick_before_move', undef,
                'assistedUnlockFlag', 'unlock_before_move', undef,
                'assistedOpenFlag', 'open_before_move', undef,
                'assistedCloseFlag', 'close_after_move', undef,
                'assistedLockFlag', 'lock_after_move', undef,
                'protectedMovesFlag', 'allow_protected_moves', undef,
                'superProtectedMovesFlag', 'allow_super_protected_moves', undef,
                'craftyMovesFlag', 'allow_crafty_moves', undef,
                'avoidHazardsFlag', 'allow_hazard_rooms', undef,
                'postProcessingFlag', 'allow_post_process', undef,
                'quickPathFindFlag', 'allow_quick_path_find', undef,
                'autocompleteExitsFlag', 'autocomplete_uncertain', undef,
                'collectCheckedDirsFlag', 'collect_checked_dirs', undef,
                'drawCheckedDirsFlag', 'draw_checked_dirs', undef,
                # ->settings8Tab
                'autoOpenWinFlag', 'auto_open_win', undef,
                'pseudoWinFlag', 'pseudo_win', undef,
                'allowTrackAloneFlag', 'keep_following', undef,
                'setTwinOrnamentFlag', 'also_set_twin_exits', undef,
                'intelligentExitsFlag', 'intelligent_uncertain', undef,
                'followAnchorFlag', 'follow_anchor', undef,
                'showAllPrimaryFlag', 'show_all_primary', undef,
                'capitalisedRoomTagFlag', 'room_tags_capitalised', undef,
#               'showTooltipsFlag', 'show_tooltips', undef,                 # Set below
                'showNotesFlag', 'show_notes', undef,
                'explainGetLostFlag', 'explain_get_lost', undef,
#               'disableUpdateModeFlag', 'disable_update_mode', undef,      # Set below
                'countVisitsFlag', 'count_char_visits', undef,
                'drawRoomEchoFlag', 'draw_room_echo', undef,
                'drawBentExitsFlag', 'draw_bent_exits', undef,
                'allowCtrlCopyFlag', 'allow_ctrl_copy', undef,
            );

            do {

                my ($iv, $menuItem, $toolbarItem);

                $iv = shift @ivList;
                $menuItem = shift @ivList;
                $toolbarItem = shift @ivList;       # 'undef' if no toolbar item

                if ($self->ivExists('editHash', $iv)) {

                    if ($self->ivShow('editHash', $iv) ne $self->editObj->$iv) {

                        $self->editObj->toggleFlag(
                            $iv,
                            $self->ivShow('editHash', $iv),
                            FALSE,                              # Don't call $self->drawRegion
                            $menuItem,
                            $toolbarItem,
                        );
                    }

                    $self->ivDelete('editHash', $iv);
                }
            } until (! @ivList);

            # Misc GA::Obj::WorldModel IVs
            if ($self->ivExists('editHash', 'firstRegion')) {

                $newValue = $self->ivShow('editHash', 'firstRegion');
                # The automapper's treeview must be redrawn
                if (defined $newValue) {

                    $regionmapObj = $self->editObj->ivShow('regionmapHash', $newValue);
                }

                # (If no ->firstRegion is set, then $regionmapObj will be 'undef')
                $self->editObj->moveRegionToTop($regionmapObj);
                $self->ivDelete('editHash', 'firstRegion');
            }

            if ($self->ivExists('editHash', 'matchDescripCharCount')) {

                $newValue = $self->ivShow('editHash', 'matchDescripCharCount');
                if ($newValue != $self->editObj->matchDescripCharCount) {

                    $self->editObj->set_matchDescripCharCount($newValue);
                }

                $self->ivDelete('editHash', 'matchDescripCharCount');
            }

            if ($self->ivExists('editHash', 'showTooltipsFlag')) {

                $self->editObj->toggleShowTooltipsFlag(
                    $self->ivShow('editHash', 'showTooltipsFlag'),
                );

                $self->ivDelete('editHash', 'showTooltipsFlag');
            }

            if ($self->ivExists('editHash', 'autoCompareMode')) {

                $self->editObj->setAutoCompareMode(
                    $self->ivShow('editHash', 'autoCompareMode'),
                );

                $self->ivDelete('editHash', 'autoCompareMode');
            }

            if ($self->ivExists('editHash', 'autoCompareAllFlag')) {

                $self->editObj->toggleAutoCompareAllFlag(
                    $self->ivShow('editHash', 'autoCompareAllFlag'),
                );

                $self->ivDelete('editHash', 'autoCompareAllFlag');
            }

            if ($self->ivExists('editHash', 'autoSlideMode')) {

                $self->editObj->setAutoSlideMode(
                    $self->ivShow('editHash', 'autoSlideMode'),
                );

                $self->ivDelete('editHash', 'autoSlideMode');
            }

            if ($self->ivExists('editHash', 'disableUpdateModeFlag')) {

                $self->editObj->toggleDisableUpdateModeFlag(
                    $self->ivShow('editHash', 'disableUpdateModeFlag'),
                );

                $self->ivDelete('editHash', 'disableUpdateModeFlag');
            }

            if ($self->ivExists('editHash', 'checkableDirMode')) {

                $self->editObj->setCheckableDirMode(
                    $self->ivShow('editHash', 'checkableDirMode'),
                );

                $self->ivDelete('editHash', 'checkableDirMode');
            }

            # Store the changes the user has made
            foreach my $key ($self->ivKeys('editHash')) {

                $self->editObj->{$key} = $self->ivShow('editHash', $key);
            }

            # Calculate new IV values for the regionmap width and height
            $self->editObj->{defaultMapWidthPixels}
                = $self->editObj->{defaultGridWidthBlocks}
                    * $self->editObj->{defaultBlockWidthPixels};
            $self->editObj->{defaultMapHeightPixels}
                = $self->editObj->{defaultGridHeightBlocks}
                    * $self->editObj->{defaultBlockHeightPixels};

            # The changes can now be cleared
            $self->ivEmpty('editHash');

            # Mark the object's corresponding file object as needing to be saved, if it exists
            if ($self->editObj->_parentFile) {

                $self->editObj->doModify('saveChanges');
            }

            # Update the current session's data viewer window, if it is open
            if ($self->session->viewerWin) {

                $self->session->viewerWin->updateNotebook();
            }

            # Redraw drawn regions in all Automapper windows using this world model. This can take
            #    a very long time, so first destroy the 'edit' window (an operation that would
            #   normally be performed by the generic ->buttonOK function)
            $self->winDestroy();
            $self->editObj->updateRegion();

            if ($failFlag) {

                $self->showMsgDialogue(
                    'Save changes',
                    'warning',
                    'You specified a room that was bigger than its gridblock; their sizes have not'
                    . ' been changed',
                    'ok',
                );
            }
        }

        return 1;
    }

    # Notebook tab

    sub generalTab {

        # General tab
        #
        # 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 . '->generalTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_General',
            ['General properties'],
        );

        # General properties
        $self->addLabel($grid, '<b>General properties</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>You can use this tab to track versions of your maps, and to claim credit for'
            . ' them</i>',
            1, 12, 1, 2,
        );
        $self->addLabel(
            $grid,
            '<i>(N.B. The date and version are not updated automatically - you can enter any values'
            . ' you like)</i>',
            1, 12, 2, 3,
        );

        $self->addLabel($grid, 'Author',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'author', 'string', 1, 128,
            3, 12, 3, 4);
        $self->addLabel($grid, 'Date',
            1, 3, 4, 5);
        $self->addEntryWithIcon($grid, 'date', 'string', 1, 64,
            3, 6, 4, 5);
        $self->addLabel($grid, 'Version',
            7, 9, 4, 5);
        $self->addEntryWithIcon($grid, 'version', 'string', 1, 64,
            9, 12, 4, 5);
        $self->addLabel($grid, 'Creation date',
            1, 3, 5, 6);
        $self->addEntry($grid, 'modelCreationDate', FALSE,
            3, 6, 5, 6);
        $self->addLabel($grid, $axmud::SCRIPT . ' Version',
            7, 9, 5, 6);
        $self->addEntry($grid, 'modelCreationVersion', FALSE,
            9, 12, 5, 6);
        $self->addLabel($grid, 'Description',
            1, 3, 6, 9);
        $self->addTextView($grid, 'descripList', TRUE,
            3, 12, 6, 9,
            TRUE, FALSE, FALSE, FALSE,  # Treat as a list, don't remove empty lines or whitespace
        );

        # Bottom row
        $self->addLabel(
            $grid,
            '<i>Location of the mudlib (if installed on this machine) and file extension (if'
            . ' required)</i>',
            1, 12, 9, 10,
        );

        $self->addLabel($grid, 'Mudlib path',
            1, 3, 10, 11);
        $self->addEntryWithIcon($grid, 'mudlibPath', 'string', 0, undef,
            3, 12, 10, 11);
        $self->addLabel($grid, 'File extension',
            1, 3, 11, 12);
        $self->addEntryWithIcon($grid, 'mudlibExtension', 'string', 0, undef,
            3, 6, 11, 12);

        # Tab complete
        return 1;
    }

    sub sizesTab {

        # Sizes tab
        #
        # 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 . '->sizesTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Sizes',
            ['Default sizes'],
        );

        # Default sizes
        $self->addLabel($grid, '<b>Default sizes</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>Default size of regionmaps, in gridblocks (use odd numbers; minimum value is'
            . ' 5)</i>',
            1, 12, 1, 2,
        );

        $self->addLabel($grid, 'Width (x axis)',
            1, 3, 2, 3);
        $self->addEntryWithIcon(
            $grid,
            'defaultGridWidthBlocks',
            'odd',
            5,
            $self->editObj->maxGridWidthBlocks,
            3, 6, 2, 3,
        );

        $self->addLabel($grid, 'Maximum width',
            7, 9, 2, 3);
        $self->addEntry($grid, 'maxGridWidthBlocks', FALSE,
            9, 12, 2, 3);

        $self->addLabel($grid, 'Height (y axis)',
            1, 3, 3, 4);
        $self->addEntryWithIcon(
            $grid,
            'defaultGridHeightBlocks',
            'odd',
            5,
            $self->editObj->maxGridHeightBlocks,
            3, 6, 3, 4,
        );

        $self->addLabel($grid, 'Maximum height',
            7, 9, 3, 4);
        $self->addEntry($grid, 'maxGridHeightBlocks', FALSE,
            9, 12, 3, 4);

        $self->addLabel(
            $grid,
            '<i>Default size of a gridblock, in pixels (use odd numbers; minimum value is 11)</i>',
            1, 12, 4, 5,
        );

        $self->addLabel($grid, 'Width',
            1, 3, 5, 6);
        $self->addEntryWithIcon(
            $grid,
            'defaultBlockWidthPixels',
            'odd',
            11,
            $self->editObj->maxBlockWidthPixels,
            3, 6, 5, 6,
        );

        $self->addLabel($grid, 'Maximum width',
            7, 9, 5, 6);
        $self->addEntry($grid, 'maxBlockWidthPixels', FALSE,
            9, 12, 5, 6);

        $self->addLabel($grid, 'Height',
            1, 3, 6, 7);
        $self->addEntryWithIcon(
            $grid,
            'defaultBlockHeightPixels',
            'odd',
            11,
            $self->editObj->maxBlockHeightPixels,
            3, 6, 6, 7,
        );

        $self->addLabel($grid, 'Maximum height',
            7, 9, 6, 7);
        $self->addEntry($grid, 'maxBlockHeightPixels', FALSE,
            9, 12, 6, 7);

        $self->addLabel(
            $grid,
            '<i>Default size of a room, in pixels (use odd numbers; minimum value is 5)</i>',
            1, 12, 7, 8,
        );

        $self->addLabel($grid, 'Width',
            1, 3, 8, 9);
        $self->addEntryWithIcon(
            $grid,
            'defaultRoomWidthPixels',
            'odd',
            5,
            $self->editObj->maxRoomWidthPixels,
            3, 6, 8, 9,
        );

        $self->addLabel($grid, 'Maximum width',
            7, 9, 8, 9);
        $self->addEntry($grid, 'maxRoomWidthPixels', FALSE,
            9, 12, 8, 9);

        $self->addLabel($grid, 'Height',
            1, 3, 9, 10);
        $self->addEntryWithIcon(
            $grid,
            'defaultRoomHeightPixels',
            'odd',
            5,
            $self->editObj->maxRoomHeightPixels,
            3, 6, 9, 10,
        );

        $self->addLabel($grid, 'Maximum height',
            7, 8, 9, 10);
        $self->addEntry($grid, 'maxRoomHeightPixels', FALSE,
            9, 12, 9, 10);

        $self->addLabel(
            $grid,
            '<i>Default size of the regionmap, in pixels (use the \'save\' button to update these'
            . ' values)</i>',
            1, 12, 10, 11,
        );
        $self->addLabel($grid, 'Width',
            1, 3, 11, 12);
        $self->addEntry($grid, 'defaultMapWidthPixels', FALSE,
            3, 6, 11, 12);
        $self->addLabel($grid, 'Height',
            7, 9, 11, 12);
        $self->addEntry($grid, 'defaultMapHeightPixels', FALSE,
            9, 12, 11, 12);

        # Tab complete
        return 1;
    }

    sub modelTab {

        # Model tab - called by $self->setupNotebook
        #
        # 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 . '->modelTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Model');

        # Add tabs to the inner notebook
        $self->model1Tab($innerNotebook);
        $self->model2Tab($innerNotebook);
        $self->model3Tab($innerNotebook);
        $self->model4Tab($innerNotebook);
        $self->model5Tab($innerNotebook);
        $self->model6Tab($innerNotebook);

        return 1;
    }

    sub model1Tab {

        # model1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->model1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Objects'],
        );

        # Objects
        $self->addLabel($grid, '<b>Objects</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<i>World model</i>',
            1, 6, 1, 2);
        $self->addLabel($grid, 'Number of objects',
            1, 3, 2, 3);
        $self->addEntry($grid, 'modelActualCount', FALSE,
            3, 6, 2, 3, 8, 8);
        $self->addLabel($grid, 'Highest object #',
            1, 3, 3, 4);
        $self->addEntry($grid, 'modelObjCount', FALSE,
            3, 6, 3, 4, 8, 8);
        $self->addLabel($grid, 'Newest object #',
            1, 3, 4, 5);
        $self->addEntry($grid, 'mostRecentNum', FALSE,
            3, 6, 4, 5, 8, 8);

        # Right column
        $self->addLabel($grid, '<i>Exit model</i>',
            7, 12, 1, 2);
        $self->addLabel($grid, 'Number of objects',
            7, 9, 2, 3);
        $self->addEntry($grid, 'exitActualCount', FALSE,
            9, 12, 2, 3, 8, 8);
        $self->addLabel($grid, 'Highest object #',
            7, 9, 3, 4);
        $self->addEntry($grid, 'exitObjCount', FALSE,
            9, 12, 3, 4, 8, 8);
        $self->addLabel($grid, 'Newest object #',
            7, 9, 4, 5);
        $self->addEntry($grid, 'mostRecentExitNum', FALSE,
            9, 12, 4, 5, 8, 8);

        # Tab complete
        return 1;
    }

    sub model2Tab {

        # model2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (@columnList, @comboList, @modList);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->model2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['List of regionmaps'],
        );

        # List of regionmaps
        $self->addLabel($grid, '<b>List of regionmaps</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of regionmaps stored in this world model, one for each region model'
            . ' object</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Region name', 'text',
            'Model #', 'text',
            'Rooms', 'int',
            'R-tags', 'int',
            'R-guilds', 'int',
            'Exits', 'int',
            'Labels', 'int',
            'Living objs', 'int',
            'Other objs', 'int',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->model2Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Edit...', 'Edit this regionmap', undef,
            1, 3, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($regionName, $regionmapObj, $childWinObj);

            ($regionName) = $self->getSimpleListData($slWidget, 0);
            if (
                defined $regionName
                && $self->editObj->ivExists('regionmapHash', $regionName)
            ) {
                $regionmapObj = $self->editObj->ivShow('regionmapHash', $regionName);

                # Open an 'edit' window for this regionmap
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::Regionmap',
                    $self,
                    $self->session,
                    'Edit regionmap \'' . $regionName . '\'',
                    $regionmapObj,
                    FALSE,                          # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs (not sure it's
                    #   necessary in this case, but we'll do it anyway, just to be safe)
                    $self->add_childDestroy(
                        $childWinObj,
                        'model2Tab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of regionmaps',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->model2Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        $self->addCheckButton(
            $grid, 'Automapper shows regions in reverse', 'reverseRegionListFlag', TRUE,
            1, 6, 11, 12);

        $self->addLabel($grid, 'First region shown',
            7, 9, 11, 12);
        @comboList
            = sort {lc($a->name) cmp lc($b->name)} ($self->editObj->ivValues('regionmapHash'));
        # Only regions which don't themselves have a parent region should be at the top of the
        #   Automapper window's list
        foreach my $regionmapObj (@comboList) {

            my $regionObj = $self->editObj->ivShow('regionModelHash', $regionmapObj->number);

            if (! $regionObj->parent) {

                push (@modList, $regionObj->name);
            }
        }

        my $combo = $self->addComboBox($grid, 'firstRegion', \@modList, '',
            FALSE,              # 'undef' value allowed
            9, 12, 11, 12);

        # Tab complete
        return 1;
    }

    sub model2Tab_refreshList {

        # Called by $self->model2Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@sortedList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->model2Tab_refreshList',
                @_,
            );
        }

        # Get a sorted list of regionmaps
        @sortedList
            = sort {lc($a->name) cmp lc($b->name)} ($self->editObj->ivValues('regionmapHash'));

        # Compile the simple list data
        foreach my $obj (@sortedList) {

            push (@dataList,
                $obj->name,
                $obj->number,
                $obj->ivPairs('gridRoomHash'),
                $obj->ivPairs('gridRoomTagHash'),
                $obj->ivPairs('gridRoomGuildHash'),
                $obj->ivPairs('gridExitHash'),
                $obj->ivPairs('gridLabelHash'),
                $obj->ivPairs('livingCountHash'),
                $obj->ivPairs('nonLivingCountHash'),
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub model3Tab {

        # model3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->model3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['List of character model objects'],
        );

        # List of character model objects
        $self->addLabel($grid, '<b>List of character model objects</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of characters (owned by any player) stored in the world model</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Name', 'text',
            'Model #', 'int',
            'Own char', 'bool',
            'Owner', 'text',
            'Diplomatic status', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->model3Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Edit...', 'Edit this character', undef,
            1, 3, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($number, $obj, $childWinObj);

            ($number) = $self->getSimpleListData($slWidget, 1);
            if ($number && $self->editObj->ivExists('modelHash', $number)) {

                $obj = $self->editObj->ivShow('modelHash', $number);

                # Open an 'edit' window for this character model object
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::ModelObj::Char',
                    $self,
                    $self->session,
                    'Edit ' . $obj->category . ' model object #' . $obj->number,
                    $obj,
                    FALSE,                          # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'model3Tab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of character objects',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->model3Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub model3Tab_refreshList {

        # Called by $self->model3Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@sortedList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->model3Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        @sortedList
            = sort {lc($a->name) cmp lc($b->name)} ($self->editObj->ivValues('knownCharHash'));

        # Compile the simple list data
        foreach my $obj (@sortedList) {

            push (@dataList,
                $obj->name,
                $obj->number,
                $obj->ownCharFlag,
                $obj->owner,
                $obj->diplomaticStatus,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub model4Tab {

        # model4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->model4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['List of minion strings'],
        );

        # List of minion strings
        $self->addLabel($grid, '<b>List of minion strings</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of strings used by the world to refer to an identifiable minion (in the world'
            . ' model)</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Model #', 'text',          # Don't show value 0, for non-model objects
            'Own minion', 'bool',
            'Obj name', 'text',
            'Minion string', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->model4Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        $self->addLabel($grid, 'Minion string',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 9, 8, 9);
        my $checkButton = $self->addCheckButton($grid, 'Own minion', undef, TRUE,
            9, 12, 8, 9);
        $checkButton->set_active(TRUE);     # Default - minion belongs to user

        my $button = $self->addButton(
            $grid,
            'Add new minion',
            'Add a new minion string corresponding to a new minion object', undef,
            1, 3, 9, 10,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($string, $cmd);

            if ($self->checkEntryIcon($entry)) {

                $string = $entry->get_text();

                # Prepare the command to send
                $cmd = 'addminionstring -a';
                if (! $checkButton->get_active()) {

                    $cmd .= ' -o';
                }

                $cmd .= ' <' . $string . '>';

                # Add the minion string
                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);

                # Refresh the simple list
                $self->model4Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        $self->addLabel($grid, 'Model number',
            3, 6, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'int', 1, undef,
            6, 9, 9, 10);

        my $button2 = $self->addButton(
            $grid,
            'Use existing minion',
            'Add a new minion string corresponding to an existing minion object', undef,
            9, 12, 9, 10,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            my ($string, $number, $cmd);

            if ($self->checkEntryIcon($entry, $entry2)) {

                $string = $entry->get_text();
                $number = $entry2->get_text();

                # Prepare the command to send
                $cmd = 'addminionstring -m ' . $number;

                if (! $checkButton->get_active()) {

                    $cmd .= ' -o';
                }

                $cmd .= ' <' . $string . '>';

                # Add the minion string
                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);

                # Refresh the simple list
                $self->model4Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button3 = $self->addButton(
            $grid,
            'Add string only',
            'Add a new minion string without a corresponding minion object', undef,
            1, 3, 10, 11,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($string, $cmd);

            if ($self->checkEntryIcon($entry)) {

                $string = $entry->get_text();

                # Prepare the command to send
                $cmd = 'addminionstring';
                if (! $checkButton->get_active()) {

                    $cmd .= ' -o';
                }

                $cmd .= ' <' . $string . '>';

                # Add the minion string
                $self->session->pseudoCmd($cmd, $self->pseudoCmdMode);

                # Refresh the simple list
                $self->model4Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button4 = $self->addButton(
            $grid,
            'Delete string',
            'Deletes the selected minion string (but not any corresponding minion object)', undef,
            3, 6, 10, 11,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            my ($string) = $self->getSimpleListData($slWidget, 3);
            if (defined $string) {

                # Delete the minion string
                $self->session->pseudoCmd(
                    'deleteminionstring <' . $string . '>',
                    $self->pseudoCmdMode,
                );

                # Refresh the simple list
                $self->model4Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button5 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of minion strings',
            undef,
            9, 12, 10, 11,
        );
        $button5->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->model4Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub model4Tab_refreshList {

        # Called by $self->model4Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @sortedList, @dataList,
            %hash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->model4Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        %hash = $self->getEditHash_hashIV('minionStringHash');
        @sortedList = sort {lc($a) cmp lc($b)} (keys %hash);

        # Compile the simple list data
        foreach my $string (@sortedList) {

            my $obj = $hash{$string};

            push (@dataList,
                $obj->number,
                $obj->ownMinionFlag,
                $obj->name,
                $string,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub model5Tab {

        # model5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->model5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['List of room tags'],
        );

        # List of room tags
        $self->addLabel($grid, '<b>List of room tags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of room tags used by rooms in the world model</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Tag', 'text',
            'Room #', 'int',
            'Room name', 'text',
            'Region', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the list
        $self->model5Tab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing buttons
        my $button = $self->addButton($grid, 'Edit room...', 'Edit the parent room', undef,
            1, 3, 10, 11);
        $button->signal_connect('clicked' => sub {

            my ($number, $obj, $childWinObj);

            ($number) = $self->getSimpleListData($slWidget, 1);
            if ($number && $self->editObj->ivExists('modelHash', $number)) {

                $obj = $self->editObj->ivShow('modelHash', $number);

                # Open an 'edit' window for this room model object
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::ModelObj::Room',
                    $self,
                    $self->session,
                    'Edit ' . $obj->category . ' model object #' . $obj->number,
                    $obj,
                    FALSE,                          # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'model5Tab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }
            }
        });

        my $button2 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of room tags',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->model5Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub model5Tab_refreshList {

        # Called by $self->model5Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @sortedList, @dataList,
            %hash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->model5Tab_refreshList',
                @_,
            );
        }

        # Import the IV
        %hash = $self->editObj->roomTagHash;
        @sortedList = sort {lc($a) cmp lc($b)} (keys %hash);

        # Compile the simple list data
        foreach my $tag (@sortedList) {

            my ($roomNum, $roomObj, $roomName, $regionObj, $regionName);

            $roomNum = $hash{$tag};
            if (defined $roomNum) {

                $roomObj = $self->editObj->ivShow('modelHash', $roomNum);
                if ($roomObj) {

                    $roomName = $roomObj->name;
                    $regionObj = $self->editObj->ivShow('modelHash', $roomObj->parent);
                    $regionName = $regionObj->name;
                }
            }

            push (@dataList,
                $tag,
                $roomNum,
                $roomName,
                $regionName,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub model6Tab {

        # model6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->model6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            ['List of teleport destinations'],
        );

        # List of teleport destinations
        $self->addLabel($grid, '<b>List of teleport destinations</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of world commands that are used by the \';teleport\' client command (see'
            . ' \';help teleport\')</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Room name', 'text',
            'World command used to get there', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'teleportHash');

        # Add entries/comboboxes for adding new variables
        $self->addLabel($grid, 'Room name',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($grid, 'Equivalent world command',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'teleportHash',
            10,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($room, $cmd);

            $room = $entry->get_text();
            $cmd = $entry2->get_text();

            if ($self->checkEntryIcon($entry, $entry2)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('teleportHash', $room, $cmd);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'teleportHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        return 1;
    }

    sub settingsTab {

        # Settings tab - called by $self->setupNotebook
        #
        # 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 . '->settingsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, 'S_ettings');

        # Add tabs to the inner notebook
        $self->settings1Tab($innerNotebook);
        $self->settings2Tab($innerNotebook);
        $self->settings3Tab($innerNotebook);
        $self->settings4Tab($innerNotebook);
        $self->settings5Tab($innerNotebook);
        $self->settings6Tab($innerNotebook);
        $self->settings7Tab($innerNotebook);
        $self->settings8Tab($innerNotebook);

        return 1;
    }

    sub settings1Tab {

        # Settings1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            [
                'Window components',
                'Label alignment',
                'Painter',
                'Show \'Working...\' dialogue',
                'Map font',
                'Tracking',
                'Synchronise grid coordinates',
            ],
        );

        # Settings (1/8)
        $self->addLabel($grid, '<b>Settings (1/8)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<u>Window components</u>',
            1, 6, 1, 2);
        $self->addCheckButton($grid, 'Show menu bar', 'showMenuBarFlag', TRUE,
            1, 6, 2, 3);
        $self->addCheckButton($grid, 'Show toolbar', 'showToolbarFlag', TRUE,
            1, 6, 3, 4);
        $self->addCheckButton($grid, 'Show region list', 'showTreeViewFlag', TRUE,
            1, 6, 4, 5);
        $self->addCheckButton($grid, 'Show map', 'showCanvasFlag', TRUE,
            1, 6, 5, 6);

        $self->addLabel($grid, '<u>Label alignment</u>',
            1, 6, 6, 7);
        $self->addCheckButton($grid, 'Labels alignored horizontally', 'mapLabelAlignXFlag', FALSE,
            1, 6, 7, 8);
        $self->addCheckButton($grid, 'Labels aligned vertically', 'mapLabelAlignYFlag', FALSE,
            1, 6, 8, 9);
        $self->addCheckButton(
            $grid,
            'Window uses multi-line input',
            'mapLabelTextViewFlag',
            FALSE,
            1, 6, 9, 10);

        $self->addLabel($grid, '<u>Painter</u>',
            1, 6, 10, 11);
        $self->addCheckButton($grid, 'Paint existing rooms', 'paintAllRoomsFlag', FALSE,
            1, 6, 11, 12);
        $self->addCheckButton($grid, 'Quick-paint without resetting', 'quickPaintMultiFlag', FALSE,
            1, 6, 12, 13);

        # Right column
       $self->addLabel($grid, '<u>Show \'Working...\' dialogue</u>',
            7, 12, 1, 2);
        $self->addLabel($grid, 'Draw objects (100+)',
            7, 9, 2, 3);
        $self->addEntryWithIcon($grid, 'drawPauseNum', 'int', 100, undef,
            9, 12, 2, 3,
            8, 8);
        $self->addLabel($grid, 'Calculate region paths (10+)',
            7, 9, 3, 4);
        $self->addEntryWithIcon($grid, 'recalculatePauseNum', 'int', 10, undef,
            9, 12, 3, 4,
            8, 8);

        $self->addLabel($grid, '<u>Map font</u>',
            7, 12, 4, 5);
        $self->addLabel($grid, 'Font',
            7, 9, 5, 6);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            9, 10, 5, 6);
        $entry4->set_text($self->editObj->mapFont);
        my $button = $self->addButton($grid, 'Modify', 'Change the automapper font', undef,
            10, 12, 5, 6);
        $button->signal_connect('clicked' => sub {

            my $newFont = $self->showFontSelectionDialogue(
                'Automapper window font',
                $self->getEditHash_scalarIV('mapFont'),
            );

            if (defined $newFont) {

                # $newFont is a string in the form 'Monospace 10'. Separate the font name from the
                #   size
                if ($newFont =~ m/(.*)\s(.\d)$/) {

                    $self->ivAdd('editHash', 'mapFont', $1);
                    $entry4->set_text($self->ivShow('editHash', 'mapFont'));
                }
            }
        });

        $self->addLabel($grid, '<u>Tracking</u>',
            7, 12, 6, 7);
        $self->addCheckButton($grid, 'Track position', 'trackPosnFlag', TRUE,
            7, 12, 7, 8);
        $self->addLabel($grid, 'Tracking sensitivity',
            7, 9, 8, 9);
        $self->addEntry($grid, 'trackingSensitivity', FALSE,
            9, 12, 8, 9,
            4, 4);

        $self->addLabel($grid, '<u>Synchronise grid coordintes</u>',
            7, 12, 9, 10);
        $self->addLabel($grid, 'Adjust X coordinate',
            7, 9, 10, 11);
        $self->addEntryWithIcon($grid, 'roomInteriorXOffset', 'int', undef, undef,
            9, 12, 10, 11,
            4, 4);
        $self->addLabel($grid, 'Adjust Y coordinate',
            7, 9, 11, 12);
        $self->addEntryWithIcon($grid, 'roomInteriorYOffset', 'int', undef, undef,
            9, 12, 11, 12,
            4, 4);

        # Tab complete
        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (%currentModeHash, %exitModeHash, %interiorModeHash);

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['Drawing modes', 'Pre-drawing of maps', 'Exits', 'Obscured exits'],
        );

        # Set up some hashes, so that entry boxes in this tab can display explanatory text
        %currentModeHash = (
            'single'        => '\'single\' - Draw normal room',
            'double'        => '\'double\' - Draw emphasised room',
            'interior'      => '\'interior\' - Draw filled-in room',
        );

        %exitModeHash = (
            'ask_regionmap' => '\'ask_regionmap\' - Let regionmaps decide',
            'no_exit'       => '\'no_exit\' - Draw no exits',
            'simple_exit'   => '\'simple_exit\' - Draw simple exits',
            'complex_exit'  => '\'complex_exit\' - Draw complex exits',
        );

        %interiorModeHash = (
            'none'          => '\'none\' - Don\'t draw counts',
            'shadow_count'  => '\'shadow_count\' - Unalloc/shadow exits',
            'region_count'  => '\'region_count\' - Region exits',
            'room_content'  => '\'room_content\' - Room contents',
            'hidden_count'  => '\'hidden_count\' - Hidden contents',
            'temp_count'    => '\'temp_count\' - Temporary contents',
            'word_count'    => '\'word_count\' - Recognised words',
            'room_tag'      => '\'room_tag\' - Room tag',
            'room_flag'     => '\'room_flag\' - Room flag text',
            'visit_count'   => '\'visit_count\' - Character visits',
            'compare_count' => '\'compare_count\' - Matching rooms',
            'profile_count' => '\'profile_count\' - Exclusive profiles',
            'title_descrip' => '\'title_descrip\' - Room descriptions',
            'exit_pattern'  => '\'exit_pattern\' - Assist move/exit patterns',
            'source_code'   => '\'source_code\' - Room source code path',
            'grid_posn'     => '\'grid_posn\' - Grid coordinates',
            'vnum'          => '\'vnum\' - Room vnum',
        );

        # Settings (2/8)
        $self->addLabel($grid, '<b>Settings (2/8)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<u>Drawing modes</u>',
            1, 6, 1, 2);
        $self->addLabel($grid, 'Current room mode',
            1, 3, 2, 3);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 2, 3);
        $entry->set_text($currentModeHash{$self->editObj->currentRoomMode});

        $self->addLabel($grid, 'Room interior mode',
            1, 3, 3, 4);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            3, 6, 3, 4);
        $entry2->set_text($interiorModeHash{$self->editObj->roomInteriorMode});

        $self->addLabel($grid, 'Exit drawing mode',
            1, 3, 4, 5);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            3, 6, 4, 5);
        $entry3->set_text($exitModeHash{$self->editObj->drawExitMode});

        $self->addLabel($grid, '<u>Pre-drawing of maps</u>',
            1, 6, 5, 6);
        $self->addCheckButton($grid, 'Allow pre-drawing in general', 'preDrawAllowFlag', TRUE,
            1, 6, 6, 7);
        $self->addLabel($grid, 'Pre-draw regions with rooms',
            1, 3, 7, 8);
        $self->addEntryWithIcon($grid, 'preDrawMinRooms', 'int', 0, undef,
            3, 6, 7, 8,
            8, 8);
        $self->addLabel($grid, 'Retain in memory if rooms',
            1, 3, 8, 9);
        $self->addEntryWithIcon($grid, 'preDrawRetainRooms', 'int', 0, undef,
            3, 6, 8, 9,
            8, 8);
        $self->addLabel($grid, '% processor time used (1-100)',
            1, 3, 9, 10);
        $self->addEntryWithIcon($grid, 'preDrawAllocation', 'int', 1, 100,
            3, 6, 9, 10,
            8, 8);

        # Right column
        $self->addLabel($grid, '<u>Exits</u>',
            7, 12, 1, 2);
        $self->addLabel($grid, 'Maximum exit length (blocks)',
            7, 9, 2, 3);
        $self->addEntry($grid, 'maxExitLengthBlocks', FALSE,
            9, 12, 2, 3,
            4, 4);
        $self->addCheckButton($grid, 'Draw exit ornaments', 'drawOrnamentsFlag', TRUE,
            7, 12, 3, 4);

        $self->addLabel($grid, '<u>Obscured exits</u>',
            7, 12, 4, 5);
        $self->addCheckButton($grid, 'Obscure unimportant exits', 'obscuredExitFlag', TRUE,
            7, 12, 5, 6);
        $self->addCheckButton($grid, 'Auto-redraw unobscured exits', 'obscuredExitRedrawFlag', TRUE,
            7, 12, 6, 7);
        $self->addLabel($grid, 'Radius of unobscured area',
            7, 9, 7, 8);
        $self->addEntryWithIcon(
            $grid,
            'obscuredExitRadius',
            'int', 1, $self->editObj->maxObscuredExitRadius,
            9, 12, 7, 8,
            4, 4);
        $self->addLabel($grid, 'Maximum radius (blocks)',
            7, 9, 8, 9);
        $self->addEntry($grid, 'maxObscuredExitRadius', FALSE,
            9, 12, 8, 9,
            4, 4);

        # Tab complete
        return 1;
    }

    sub settings3Tab {

        # Settings3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _3',
            ['Room matching', 'Room updating'],
        );

        # Settings (3/8)
        $self->addLabel($grid, '<b>Settings (3/8)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<u>Room matching</u>',
            1, 6, 1, 2);
        $self->addCheckButton($grid, 'Match room titles', 'matchTitleFlag', TRUE,
            1, 6, 2, 3);
        $self->addCheckButton($grid, 'Match (verbose) descriptions', 'matchDescripFlag', TRUE,
            1, 6, 3, 4);
        $self->addLabel($grid, 'Characters to match (0 - match whole)',
            1, 4, 4, 5);
        $self->addEntryWithIcon(
            $grid, 'matchDescripCharCount', 'int', 0, undef,
            4, 6, 4, 5, 4, 4);
        $self->addCheckButton($grid, 'Match exits', 'matchExitFlag', TRUE,
            1, 6, 5, 6);
        $self->addCheckButton($grid, 'Analyse verbose descriptions', 'analyseDescripFlag', TRUE,
            1, 6, 6, 7);
        $self->addCheckButton($grid, 'Match room source code path', 'matchSourceFlag', TRUE,
            1, 6, 7, 8);
        $self->addCheckButton($grid, 'Match world\'s room vnum', 'matchVNumFlag', TRUE,
            1, 6, 8, 9);

        # Right column
        $self->addLabel($grid, '<u>Room updating</u>',
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Update room titles', 'updateTitleFlag', TRUE,
            7, 12, 2, 3);
        $self->addCheckButton($grid, 'Update room descriptions', 'updateDescripFlag', TRUE,
            7, 12, 3, 4);
        $self->addCheckButton($grid, 'Update exits', 'updateExitFlag', TRUE,
            7, 12, 4, 5);
        $self->addCheckButton(
            $grid, 'Update exit ornaments using exit states', 'updateOrnamentFlag', TRUE,
            7, 12, 5, 6);
        $self->addCheckButton($grid, 'Update room source code path', 'updateSourceFlag', TRUE,
            7, 12, 6, 7);
        $self->addCheckButton($grid, 'Update world\'s room vnum', 'updateVNumFlag', TRUE,
            7, 12, 7, 8);
        $self->addCheckButton($grid, 'Update room commands', 'updateRoomCmdFlag', TRUE,
            7, 12, 8, 9);

        # Tab complete
        return 1;
    }

    sub settings4Tab {

        # Settings4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings4Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _4',
            ['Auto-compare mode', 'Auto-slide mode', 'Auto-rescue mode'],
        );

        # Settings (4/8)
        $self->addLabel($grid, '<b>Settings (4/8)</b>',
            0, 12, 0, 1);

        # Auto-compare mode
        $self->addLabel($grid, '<u>Auto-compare mode</u>',
            1, 6, 1, 2);

        my ($radioGroup, $radioButton, $radioButton2, $radioButton3);
        ($radioGroup, $radioButton) = $self->addRadioButton($grid,
            undef, 'Don\'t auto-compare the current room', 'autoCompareMode', 'default', TRUE,
            1, 6, 2, 3);
        ($radioGroup, $radioButton2) = $self->addRadioButton($grid,
            $radioGroup, 'Auto-compare new rooms', 'autoCompareMode', 'new', TRUE,
            1, 6, 3, 4);
        ($radioGroup, $radioButton3) = $self->addRadioButton($grid,
            $radioGroup, 'Auto-compare the current room', 'autoCompareMode', 'current', TRUE,
            1, 6, 4, 5);

        $self->addLabel($grid, '<i>Which rooms to compare</i>',
            1, 6, 5, 6);

        my ($radioGroup2, $radioButton11, $radioButton12);
        ($radioGroup2, $radioButton11) = $self->addRadioButton($grid,
            undef, 'Compare with rooms in the same region', undef, undef, TRUE,
            1, 6, 6, 7);
        ($radioGroup2, $radioButton12) = $self->addRadioButton($grid,
            $radioGroup2, 'Compare with rooms in the whole world', undef, undef, TRUE,
            1, 6, 7, 8);
        if ($self->getEditHash_scalarIV('autoCompareAllFlag')) {

            $radioButton12->set_active(TRUE);
        }

        # (->autoCompareAllFlag is a flag, but it's easier for the user if it's presented using
        #   radio buttons)
        $radioButton11->signal_connect('toggled' => sub {

            if ($radioButton11->get_active()) {

                $self->ivAdd('editHash', 'autoCompareAllFlag', FALSE);
            }
        });

        $radioButton12->signal_connect('toggled' => sub {

            if ($radioButton12->get_active()) {

                $self->ivAdd('editHash', 'autoCompareAllFlag', TRUE);
            }
        });

        $self->addLabel($grid, '<i>Max comparisons (0 - no limit)</i>',
            1, 4, 9, 10);
        $self->addEntryWithIcon($grid, 'autoCompareMax', 'int', 0, undef,
            4, 6, 9, 10, 8, 8);

        # Auto-rescue mode
        $self->addLabel($grid, '<u>Auto-rescue mode</u>',
            1, 6, 10, 11);

        $self->addCheckButton($grid, 'Enable auto-rescue mode when lost', 'autoRescueFlag', TRUE,
            1, 2, 11, 12);
        $self->addCheckButton(
            $grid, 'Auto-merge rooms at first matching room', 'autoRescueFirstFlag', TRUE,
            1, 2, 12, 13);
        $self->addCheckButton(
            $grid, 'Prompt user before auto-merge rooms', 'autoRescuePromptFlag', TRUE,
            1, 2, 13, 14);

        # Auto-slide mode
        $self->addLabel($grid, '<u>Auto-slide mode</u>',
            7, 12, 1, 2);

        my (
            $radioGroup3, $radioButton21, $radioButton22, $radioButton23, $radioButton24,
            $radioButton25, $radioButton26, $radioButton27,
        );

        ($radioGroup3, $radioButton21) = $self->addRadioButton($grid,
            undef, 'Don\'t auto-slide new rooms', 'autoSlideMode', 'default', TRUE,
            7, 12, 2, 3);
        ($radioGroup3, $radioButton22) = $self->addRadioButton($grid,
            $radioGroup3, 'Slide original room backwards', 'autoSlideMode', 'orig_pull', TRUE,
            7, 12, 3, 4);
        ($radioGroup3, $radioButton23) = $self->addRadioButton($grid,
            $radioGroup3, 'Slide original room forwards', 'autoSlideMode', 'orig_push', TRUE,
            7, 12, 4, 5);
        ($radioGroup3, $radioButton24) = $self->addRadioButton($grid,
            $radioGroup3, 'Slide blocking room backwards', 'autoSlideMode', 'other_pull', TRUE,
            7, 12, 5, 6);
        ($radioGroup3, $radioButton25) = $self->addRadioButton($grid,
            $radioGroup3, 'Slide blocking room forwards', 'autoSlideMode', 'other_push', TRUE,
            7, 12, 6, 7);
        ($radioGroup3, $radioButton26) = $self->addRadioButton($grid,
            $radioGroup3, 'Slide new room backwards', 'autoSlideMode', 'dest_pull', TRUE,
            7, 12, 7, 8);
        ($radioGroup3, $radioButton27) = $self->addRadioButton($grid,
            $radioGroup3, 'Slide new room forwards', 'autoSlideMode', 'dest_push', TRUE,
            7, 12, 8, 9);
        $self->addLabel($grid, '<i>Max slide distance (1 or above)</i>',
            7, 10, 9, 10);
        $self->addEntryWithIcon($grid, 'autoSlideMax', 'int', 1, undef,
            10, 12, 9, 10, 8, 8);

        # (Auto-rescue mode continued, right column)
        $self->addCheckButton(
            $grid, 'Don\'t move non-matching rooms', 'autoRescueNoMoveFlag', TRUE,
            7, 8, 11, 12);
        $self->addCheckButton(
            $grid, 'Only update visits in merged rooms', 'autoRescueVisitsFlag', TRUE,
            7, 8, 12, 13);
        $self->addCheckButton(
            $grid, 'Temporarily switch to \'update\' mode', 'autoRescueForceFlag', TRUE,
            7, 8, 13, 14);

        # Tab complete
        return 1;
    }

    sub settings5Tab {

        # Settings5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings5Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _5',
            ['Axbasic scripts'],
        );

        # Settings (5/8)
        $self->addLabel($grid, '<b>Settings (5/8)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel(
            $grid,
            '<u>' . $axmud::BASIC_NAME . ' scripts run when entering new rooms</u>',
            1, 10, 1, 2);
        $self->addTextView($grid, 'newRoomScriptList', TRUE,
            1, 10, 2, 10,
            TRUE, TRUE, TRUE, FALSE,    # Treat as a list, remove empty lines, remove whitespace
        );

        $self->addCheckButton(
            $grid,
            'Allow scripts applying to all rooms in the world model above (including the scripts'
            . ' listed above)',
            'allowModelScriptFlag',
            TRUE,
            1, 12, 10, 11);
        $self->addCheckButton(
            $grid,
            'Allow invididual room scripts (which run before the scripts listed above)',
            'allowRoomScriptFlag',
            TRUE,
            1, 12, 11, 12);

        # Right column
        $self->addLabel(
            $grid,
            '<u>' . $axmud::BASIC_NAME . ' scripts run when entering existing rooms</u>',
            10, 12, 1, 2);
        $self->addTextView($grid, 'arriveScriptList', TRUE,
            10, 12, 2, 10,
            TRUE, TRUE, TRUE, FALSE,    # Treat as a list, remove empty lines, remove whitespace
        );

        # Tab complete
        return 1;
    }

    sub settings6Tab {

        # Settings6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings6Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _6',
            [
                'Searches',
                'Locate room',
                'Locate room after random exit',
                'Pathfinding',
                'Simple pathfinding',
            ],
        );

        # Settings (6/8)
        $self->addLabel($grid, '<b>Settings (6/8)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<u>Searches (0 - no limit)</u>',
            1, 6, 1, 2);
        $self->addLabel($grid, 'Max objects searched',
            1, 3, 2, 3);
        $self->addEntryWithIcon($grid, 'searchMaxObjects', 'int', 0, undef,
            3, 6, 2, 3, 4, 4);
        $self->addLabel($grid, 'Maximum matches',
            1, 3, 3, 4);
        $self->addEntryWithIcon($grid, 'searchMaxMatches', 'int', 0, undef,
            3, 6, 3, 4, 4, 4);
        $self->addCheckButton($grid, 'Select matching rooms', 'searchSelectRoomsFlag', TRUE,
            1, 6, 4, 5);

        $self->addLabel($grid, '<u>Locate room (0 - no limit)</u>',
            1, 6, 5, 6);
        $self->addLabel($grid, 'Max rooms compared',
            1, 3, 6, 7);
        $self->addEntryWithIcon($grid, 'locateMaxObjects', 'int', 0, undef,
            3, 6, 6, 7, 8, 8);
        $self->addLabel($grid, '<u>Locate room after random exit</u>',
            1, 6, 7, 8);
        $self->addCheckButton($grid, 'Locate in region', 'locateRandomInRegionFlag', TRUE,
            1, 6, 8, 9);
        $self->addCheckButton($grid, 'Locate anywhere', 'locateRandomAnywhereFlag', TRUE,
            1, 6, 9, 10);

        # Right column
        $self->addLabel($grid, '<u>Pathfinding</u>',
            7, 12, 1, 2);
        $self->addCheckButton($grid, 'Avoid hazards', 'avoidHazardsFlag', TRUE,
            7, 12, 2, 3);
        $self->addCheckButton($grid, 'Apply post-processing to paths', 'postProcessingFlag', TRUE,
            7, 12, 3, 4);
        $self->addCheckButton($grid, 'Double-click pathfinding', 'quickPathFindFlag', TRUE,
            7, 12, 4, 5);
        $self->addLabel($grid, 'Maximum steps in path (0 - no limit)',
            7, 10, 5, 6);
        $self->addEntryWithIcon($grid, 'pathFindStepLimit', 'int', 0, undef,
            10, 12, 5, 6, 8, 8);

        $self->addLabel($grid, '<u>Simple pathfinding - adjacent region mode</u>',
            7, 12, 6, 7);

        my ($radioGroup, $radioButton, $radioButton2, $radioButton3);
        ($radioGroup, $radioButton) = $self->addRadioButton($grid,
            undef, 'Path must be in a single region', 'adjacentMode', 'default', TRUE,
            7, 12, 7, 8);
        ($radioGroup, $radioButton2) = $self->addRadioButton($grid,
            $radioGroup, 'Path can use adjacent regions', 'adjacentMode', 'near', TRUE,
            7, 12, 8, 9);
        ($radioGroup, $radioButton3) = $self->addRadioButton($grid,
            $radioGroup, 'Path can use all regions', 'adjacentMode', 'all', TRUE,
            7, 12, 9, 10);
        $self->addLabel($grid, 'Adjacent regions to use (0 - none)',
            7, 10, 10, 11);
        $self->addEntryWithIcon($grid, 'adjacentCount', 'int', 0, undef,
            10, 12, 10, 11, 4, 4);

        # Tab complete
        return 1;
    }

    sub settings7Tab {

        # Settings7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $useIndex,
            @list, @comboList,
            %descripHash,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings7Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _7',
            [
                'Assisted moves',
                'Protected moves',
                'Crafty moves',
                'Exit lengths',
                'Checked directions',
            ],
        );

        # Settings (7/8)
        $self->addLabel($grid, '<b>Settings (7/8)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<u>Assisted moves</u>',
            1, 6, 1, 2);
        my $checkButton = $self->addCheckButton($grid, 'Assisted moves enabled', undef, TRUE,
            1, 6, 2, 3);
        $checkButton->set_active($self->editObj->assistedMovesFlag);
        # (->signal_connect appears below)

        $self->addCheckButton($grid, 'Break down doors', 'assistedBreakFlag', TRUE,
            1, 6, 3, 4);
        $self->addCheckButton($grid, 'Pick locks', 'assistedPickFlag', TRUE,
            1, 6, 4, 5);
        $self->addCheckButton($grid, 'Unlock doors', 'assistedUnlockFlag', TRUE,
            1, 6, 5, 6);
        $self->addCheckButton($grid, 'Open doors', 'assistedOpenFlag', TRUE,
            1, 6, 6, 7);
        $self->addCheckButton($grid, 'Close doors', 'assistedCloseFlag', TRUE,
            1, 6, 7, 8);
        $self->addCheckButton($grid, 'Lock doors', 'assistedLockFlag', TRUE,
            1, 6, 8, 9);

        $self->addLabel($grid, '<u>Protected moves</u>',
            1, 6, 9, 10);

        my $checkButton2 = $self->addCheckButton(
            $grid, 'Protected moves enabled', 'protectedMovesFlag', TRUE,
            1, 6, 10, 11);
        # ->signal_connect appears below
        if (! $self->editObj->assistedMovesFlag) {

            $checkButton2->set_state('insensitive');
        }

        my $checkButton3 = $self->addCheckButton(
            $grid, 'Cancel commands when move overruled', 'superProtectedMovesFlag', TRUE,
            1, 6, 11, 12);
        if (! $self->editObj->assistedMovesFlag) {

            $checkButton3->set_state('insensitive');
        }

        # Right column
        $self->addLabel($grid, '<u>Crafty moves</u>',
            7, 12, 1, 2);

        my $checkButton4 = $self->addCheckButton(
            $grid, 'Crafty moves enabled', 'craftyMovesFlag', TRUE,
            7, 12, 2, 3);
        if ($self->editObj->protectedMovesFlag) {

            $checkButton4->set_state('insensitive');
        }

        $self->addLabel($grid, '<u>Exit lengths</u>',
            7, 12, 3, 4);
        $self->addLabel($grid, 'Horizontal exit length (blocks)',
            7, 9, 4, 5);
        $self->addEntryWithIcon(
            $grid,
            'horizontalExitLengthBlocks',
            'int', 1, $self->editObj->maxExitLengthBlocks,
            9, 12, 4, 5, 2, 2);
        $self->addLabel($grid, 'Vertical exit length (blocks)',
            7, 9, 5, 6);
        $self->addEntryWithIcon(
            $grid,
            'verticalExitLengthBlocks',
            'int', 1, $self->editObj->maxExitLengthBlocks,
            9, 12, 5, 6, 2, 2);
        $self->addLabel($grid, 'Maximum exit length (blocks)',
            7, 9, 6, 7);
        $self->addEntry($grid, 'maxExitLengthBlocks', FALSE,
            9, 12, 6, 7, 4, 4);

        $self->addLabel($grid, '<u>Checked directions</u>',
            7, 12, 7, 8);
        $self->addCheckButton($grid, 'Collect checked directions', 'collectCheckedDirsFlag', TRUE,
            7, 12, 8, 9);
        $self->addCheckButton($grid, 'Draw checked directions', 'drawCheckedDirsFlag', TRUE,
            7, 12, 9, 10);
        $self->addLabel($grid, 'Checkable directions to count',
            7, 12, 10, 11);

        @list = (
            'simple', '\'simple\' - count NSEW', 0,
            'diku', '\'diku\' - count NSEWUD', 1,
            'lp', '\'lp\' - count NSEWUD, NE/NW/SE/SW', 2,
            'complex', '\'complex\' - count all primary directions', 3,
        );

        do {

            my $value = shift @list;
            my $descrip = shift @list;
            my $index = shift @list;

            push (@comboList, $descrip);
            $descripHash{$descrip} = $value;

            if ($self->editObj->checkableDirMode eq $value) {

                $useIndex = $index;
            }

        } until (! @list);

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            7, 10, 11, 12);
        $combo->set_active($useIndex);
        $combo->signal_connect('changed' => sub {

            my $text = $combo->get_active_text();

            $self->ivAdd('editHash', 'checkableDirMode', $descripHash{$text});
        });

        # ->signal_connects from above
        $checkButton->signal_connect('toggled' => sub {

            if ($checkButton->get_active()) {

                $self->ivAdd('editHash', 'assistedMovesFlag', TRUE);
                $checkButton2->set_sensitive(TRUE);
                $checkButton3->set_sensitive(TRUE);

            } else {

                $self->ivAdd('editHash', 'assistedMovesFlag', FALSE);
                $checkButton2->set_sensitive(FALSE);
                $checkButton3->set_sensitive(FALSE);
            }
        });

        $checkButton2->signal_connect('toggled' => sub {

            if ($checkButton2->get_active()) {

                $self->ivAdd('editHash', 'protectedMovesFlag', TRUE);
                $checkButton4->set_sensitive(FALSE);

            } else {

                $self->ivAdd('editHash', 'protectedMovesFlag', FALSE);
                $checkButton4->set_sensitive(TRUE);
            }
        });

        # Tab complete
        return 1;
    }

    sub settings8Tab {

        # Settings8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->settings8Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _8',
            ['Miscellaneous flags'],
        );

        # Settings (8/8)
        $self->addLabel($grid, '<b>Settings (8/8)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($grid, '<u>Miscellaneous flags</u>',
            1, 6, 1, 2);
        $self->addCheckButton($grid, 'Open window automatically', 'autoOpenWinFlag', TRUE,
            1, 6, 2, 3);
        $self->addCheckButton($grid, 'Open as pseudo-window (if possible)', 'pseudoWinFlag', TRUE,
            1, 6, 3, 4);
        $self->addCheckButton(
            $grid, 'Keep following character after closing', 'allowTrackAloneFlag', TRUE,
            1, 6, 4, 5);
        $self->addCheckButton($grid, 'Set twin exit ornaments', 'setTwinOrnamentFlag', TRUE,
            1, 6, 5, 6);
        $self->addCheckButton(
            $grid, 'Auto-complete uncertain exits', 'autocompleteExitsFlag', TRUE,
            1, 6, 6, 7);
        $self->addCheckButton($grid, 'Intelligent uncertain exits', 'intelligentExitsFlag', TRUE,
            1, 6, 7, 8);
        $self->addCheckButton($grid, 'Draw new exits for follow anchors', 'followAnchorFlag', TRUE,
            1, 6, 8, 9);
        $self->addCheckButton($grid, 'Auto-set tags for region exits', 'updateExitTagFlag', TRUE,
            1, 6, 9, 10);
        $self->addCheckButton(
            $grid, 'Show all 18 primary directions in dialogues', 'showAllPrimaryFlag', TRUE,
            1, 6, 10, 11);

        # Right column
        $self->addCheckButton($grid, 'Capitalise room tags', 'capitalisedRoomTagFlag', TRUE,
            7, 12, 2, 3);
        $self->addCheckButton($grid, 'Show tooltips', 'showTooltipsFlag', TRUE,
            7, 12, 3, 4);
        $self->addCheckButton($grid, 'Show room notes in tooltips', 'showNotesFlag', TRUE,
            7, 12, 4, 5);
        $self->addCheckButton($grid, 'Show explanation when lost', 'explainGetLostFlag', TRUE,
            7, 12, 5, 6);
        $self->addCheckButton($grid, 'Update mode disabled', 'disableUpdateModeFlag', TRUE,
            7, 12, 6, 7);
        $self->addCheckButton($grid, 'Count character visits', 'countVisitsFlag', TRUE,
            7, 12, 7, 8);
        $self->addCheckButton($grid,  'Draw room echos', 'drawRoomEchoFlag', TRUE,
            7, 12, 8, 9);
        $self->addCheckButton($grid, 'Draw bent broken exits', 'drawBentExitsFlag', TRUE,
            7, 12, 9, 10);
        $self->addCheckButton(
            $grid, 'Use CTRL+C for \'move rooms to click\'',  'allowCtrlCopyFlag', TRUE,
            7, 12, 10, 11);

        # Tab complete
        return 1;
    }

    sub coloursTab {

        # Colours tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->coloursTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Colours',
            ['Region colour schemes'],
        );

        # Region colour schemes
        $self->addLabel($grid, '<b>Region colour schemes</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of colour schemes used by one or more regions</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Default', 'bool',
            'Scheme name', 'text',
            'Used by', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 11);

        # Initialise the list
        $self->coloursTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Scheme name',
            1, 2, 11, 12);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            2, 4, 11, 12);

        my $button = $self->addButton($grid,
            'Add...', 'Add a region colour scheme', undef,
            4, 6, 11, 12,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($schemeName, $childWinObj);

            if ($self->checkEntryIcon($entry)) {

                $schemeName = $entry->get_text();

                # Check the scheme doesn't already exist
                if ($self->editObj->ivExists('regionSchemeHash', $schemeName)) {

                    # Empty the entry box
                    $entry->set_text('');

                } else {

                    # Create a new scheme
                    $self->session->pseudoCmd(
                        'addregionscheme <' . $schemeName . '>',
                        $self->pseudoCmdMode,
                    );

                    # If the scheme was created...
                    if ($self->editObj->ivExists('regionSchemeHash', $schemeName)) {

                        # Open an 'edit' window for the user to customise the scheme
                        $childWinObj = $self->createFreeWin(
                            'Games::Axmud::EditWin::RegionScheme',
                            $self,
                            $self->session,
                            'Edit region colour scheme \'' . $schemeName . '\'',
                            $self->editObj->ivShow('regionSchemeHash', $schemeName),
                            FALSE,                  # Not temporary
                        );

                        if ($childWinObj) {

                            # When the 'edit' window closes, update widgets and/or IVs
                            $self->add_childDestroy(
                                $childWinObj,
                                'coloursTab_refreshList',
                                [$slWidget, (scalar @columnList / 2)],
                            );
                        }

                        # Reset the list to show the scheme (immediately)
                        $self->coloursTab_refreshList($slWidget, (scalar @columnList / 2));
                    }
                }
            }
        });

        my $button2 = $self->addButton(
            $grid, 'Edit...', 'Edit the selected region colour scheme', undef,
            6, 8, 11, 12);
        $button2->signal_connect('clicked' => sub {

            my ($schemeName, $childWinObj);

            ($schemeName) = $self->getSimpleListData($slWidget, 1);
            if (defined $schemeName && $self->editObj->ivExists('regionSchemeHash', $schemeName)) {

                # Open an 'edit' window
                $childWinObj = $self->createFreeWin(
                    'Games::Axmud::EditWin::RegionScheme',
                    $self,
                    $self->session,
                    'Edit region colour scheme \'' . $schemeName . '\'',
                    $self->editObj->ivShow('regionSchemeHash', $schemeName),
                    FALSE,                  # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'coloursTab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }
            }
        });

        my $button3 = $self->addButton(
            $grid, 'Delete', 'Delete the selected region colour scheme', undef,
            8, 10, 11, 12,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($schemeName) = $self->getSimpleListData($slWidget, 1);
            if (defined $schemeName) {

                # Delete the scheme
                $self->session->pseudoCmd(
                    'deleteregionscheme <' . $schemeName . '>',
                    $self->pseudoCmdMode,
                );

                # Reset the list to remove the deleted scheme
                $self->coloursTab_refreshList($slWidget, (scalar @columnList / 2));
            }
        });

        my $button4 = $self->addButton($grid,
            'Refresh list', 'Refresh the list of schemes', undef,
            10, 12, 11, 12);
        $button4->signal_connect('clicked' => sub {

            $self->coloursTab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub coloursTab_refreshList {

        # Resets the simple list displayed by $self->coloursTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @schemeList, @dataList,
            %schemeHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->coloursTab_refreshList', @_);
        }

        # Get a sorted list of region schemes, and put the default scheme at the beginning of the
        #   list
        foreach my $schemeObj ($self->editObj->ivValues('regionSchemeHash')) {

            if ($schemeObj->name ne 'default') {

                push (@schemeList, $schemeObj);
            }
        }

        @schemeList = sort {lc($a->name) cmp lc($b->name)} (@schemeList);
        unshift(@schemeList, $self->editObj->defaultSchemeObj);

        # Compile a hash of region schemes and the regions that use them, in the form
        #   $schemeHash{scheme_name} = reference_to_list_of_region_names
        foreach my $schemeObj ($self->editObj->ivValues('regionSchemeHash')) {

            $schemeHash{$schemeObj->name} = [];
        }

        foreach my $regionmapObj ($self->editObj->ivValues('regionmapHash')) {

            my ($schemeName, $listRef);

            if (! defined $regionmapObj->regionScheme) {
                $schemeName = 'default';
            } else {
                $schemeName = $regionmapObj->regionScheme;
            }

            if (! exists $schemeHash{$schemeName}) {

                $schemeHash{$schemeName} = [ $regionmapObj->name ];

            } else {

                $listRef = $schemeHash{$schemeName};
                push (@$listRef, $regionmapObj->name);
            }
        }

        # Compile the simple list data
        foreach my $schemeObj (@schemeList) {

            my ($defaultFlag, $listRef, $string);

            if ($schemeObj->name eq 'default') {
                $defaultFlag = TRUE;
            } else {
                $defaultFlag = FALSE;
            }

            if (! exists $schemeHash{$schemeObj->name}) {
                $string = '';
            } else {
                $listRef = $schemeHash{$schemeObj->name};
                $string = join(', ', sort {lc($a) cmp lc($b)} (@$listRef));
            }

            push (@dataList,
                $defaultFlag,
                $schemeObj->name,
                $string,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub stylesTab {

        # Styles tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->stylesTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Label styles',
            ['Label styles'],
        );

        # Label styles
        $self->addLabel($grid, '<b>Label styles</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of label styles that can be applied to multiple labels throughout the map</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Style name', 'text',
            'Text colour', 'text',
            'Underlay', 'text',
            'Ital', 'bool',
            'Bold', 'bool',
            'Under', 'bool',
            'Strike', 'bool',
            'Box', 'bool',
            'Size', 'text',
#            'Gravity', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 11);

        # Initialise the list
        $self->stylesTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add editing widgets
        $self->addLabel($grid, 'Style name',
            1, 2, 11, 12);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            2, 4, 11, 12);

        my $button = $self->addButton($grid,
            'Add...', 'Add a label style', undef,
            4, 6, 11, 12,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my ($styleName, $childWinObj);

            if ($self->checkEntryIcon($entry)) {

                $styleName = $entry->get_text();

                # Check the style doesn't already exist
                if ($self->editObj->ivExists('mapLabelStyleHash', $styleName)) {

                    # Empty the entry box
                    $entry->set_text('');

                } else {

                    # Create a new style
                    $self->session->pseudoCmd(
                        'addlabelstyle <' . $styleName . '>',
                        $self->pseudoCmdMode,
                    );

                    # If the style was created...
                    if ($self->editObj->ivExists('mapLabelStyleHash', $styleName)) {

                        # Open an 'edit' window for the user to customise the style
                        $childWinObj = $self->createFreeWin(
                            'Games::Axmud::EditWin::MapLabelStyle',
                            $self,
                            $self->session,
                            'Edit map label style \'' . $styleName . '\'',
                            $self->editObj->ivShow('mapLabelStyleHash', $styleName),
                            FALSE,                  # Not temporary
                        );

                        if ($childWinObj) {

                            # When the 'edit' window closes, update widgets and/or IVs
                            $self->add_childDestroy(
                                $childWinObj,
                                'stylesTab_refreshList',
                                [$slWidget, (scalar @columnList / 2)],
                            );
                        }

                        # Reset the list to show the style (immediately)
                        $self->stylesTab_refreshList($slWidget, (scalar @columnList / 2));
                    }
                }
            }
        });

        my $button2 = $self->addButton($grid, 'Edit...', 'Edit the selected label style', undef,
            6, 8, 11, 12);
        $button2->signal_connect('clicked' => sub {

            my ($styleName, $childWinObj);

            ($styleName) = $self->getSimpleListData($slWidget, 0);
            if (defined $styleName && $self->editObj->ivExists('mapLabelStyleHash', $styleName)) {

                # Open an 'edit' window
                $self->createFreeWin(
                    'Games::Axmud::EditWin::MapLabelStyle',
                    $self,
                    $self->session,
                    'Edit map label style \'' . $styleName . '\'',
                    $self->editObj->ivShow('mapLabelStyleHash', $styleName),
                    FALSE,                  # Not temporary
                );

                if ($childWinObj) {

                    # When the 'edit' window closes, update widgets and/or IVs
                    $self->add_childDestroy(
                        $childWinObj,
                        'stylesTab_refreshList',
                        [$slWidget, (scalar @columnList / 2)],
                    );
                }
            }
        });

        my $button3 = $self->addButton($grid, 'Delete', 'Delete the selected label style', undef,
            8, 10, 11, 12,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my ($styleName) = $self->getSimpleListData($slWidget, 0);
            if (defined $styleName) {

                # Delete the style
                $self->session->pseudoCmd(
                    'deletelabelstyle <' . $styleName . '>',
                    $self->pseudoCmdMode,
                );

                # Reset the list to remove the deleted style
                $self->stylesTab_refreshList($slWidget, (scalar @columnList / 2));
            }
        });

        my $button4 = $self->addButton($grid,
            'Refresh list', 'Refresh the list of styles', undef,
            10, 12, 11, 12);
        $button4->signal_connect('clicked' => sub {

            $self->stylesTab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Tab complete
        return 1;
    }

    sub stylesTab_refreshList {

        # Resets the simple list displayed by $self->stylesTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (@styleList, @dataList);

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->stylesTab_refreshList', @_);
        }

        # Get a sorted list of styles
        @styleList
            = sort {lc($a->name) cmp lc($b->name)} ($self->editObj->ivValues('mapLabelStyleHash'));

        # Compile the simple list data
        foreach my $styleObj (@styleList) {

            push (@dataList,
                $styleObj->name,
                $styleObj->textColour,
                $styleObj->underlayColour,
                $styleObj->italicsFlag,
                $styleObj->boldFlag,
                $styleObj->underlineFlag,
                $styleObj->strikeFlag,
                $styleObj->boxFlag,
                $styleObj->relSize,
#                $styleObj->gravity,
            );
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub roomFlagsTab {

        # RoomFlags tab - called by $self->setupNotebook
        #
        # 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 . '->roomFlagsTab', @_);
        }

        # Tab setup
        # Create a notebook within the main one, so that we have two rows of tabs
        my $innerNotebook = $self->addInnerNotebookTabs($self->notebook, '_Room flags');

        # Add tabs to the inner notebook
        $self->roomFlags1Tab($innerNotebook);
        $self->roomFlags2Tab($innerNotebook);

        $self->roomFlags3Tab(
            $innerNotebook,
           3,
            'Painting by room titles',
            'List of patterns matching a room\'s title, and the corresponding room flag used'
            . ' when the painter is on',
            'paintFromTitleHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            4,
            'Painting by room descriptions',
            'List of patterns matching a room\'s description, and the corresponding room flag'
            . ' used when the painter is on',
            'paintFromDescripHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            5,
            'Painting by room exits',
            'List of patterns matching one of the room\'s exits, and the corresponding room flag'
            . ' used when the painter is on',
            'paintFromExitHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            6,
            'Painting by room contents',
            'List of patterns matching an object in a room, and the corresponding room flag used'
            . ' when the painter is on',
            'paintFromObjHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            7,
            'Painting by room commands',
            'List of patterns matching a room command, and the corresponding room flag used when'
            . ' the painter is on',
            'paintFromRoomCmdHash',
        );

        return 1;
    }

    sub roomFlags1Tab {

        # RoomFlags1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $sortMode,
            @list, @comboList, @columnList,
            %descripHash,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->roomFlags1Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _1',
            ['Room flags'],
        );

        # Room flags
        $self->addLabel($grid, '<b>Room flags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of flags that mark a room as being of a particular type</i>',
            1, 8, 1, 2,
        );

        @list = (
            'default'   => 'Show all room flags',
            'essential' => 'Hide non-essential room flags',
            'custom'    => 'Show only custom room flags',
        );

        do {

            my ($mode, $descrip);

            $mode = shift @list;
            $descrip = shift @list;

            $descripHash{$descrip} = $mode;
            push (@comboList, $descrip);

        } until (! @list);

        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            8, 12, 1, 2);
        # (->signal_connect appears below)
        if ($self->editObj->roomFlagShowMode eq 'essential') {
            $combo->set_active(1);
        } elsif ($self->editObj->roomFlagShowMode eq 'custom') {
            $combo->set_active(2);
        } else {
            $combo->set_active(0);
        }

        # Add a simple list
        @columnList = (
            'Flag', 'text',
            'Custom', 'bool',
            'Abbrev', 'text',
            'Priority', 'int',
            'Filter', 'text',
            'Colour', 'text',
            'Description', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 9);

        # Initialise the list
        $sortMode = 'priority';      # Sort by priority
        $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);

        # Add entry boxes, canvases and editing buttons
        my $entry = $self->addEntry($grid, undef, FALSE,
            1, 3, 9, 10,
            16, 16);

        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($grid, undef, undef,
            3, 4, 9, 10);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            4, 6, 9, 10,
            7, 7);
        my $button = $self->addButton($grid, 'Change', 'Change this colour', undef,
            6, 8, 9, 10,
            TRUE);              # Irreversible

        my $button2 = $self->addButton($grid, 'Use default:', 'Use the default colour', undef,
            8, 10, 9, 10,
            TRUE);              # Irreversible
        my ($frame2, $canvas2, $canvasObj2) = $self->addSimpleCanvas($grid, undef, undef,
            10, 11, 9, 10);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            11, 12, 9, 10,
            7, 7);

        my $button3 = $self->addButton(
            $grid,
            'Move up',
            'Move this flag up the priority list',
            undef,
            1, 3, 10, 11,
            TRUE,               # Irreversible
        );

        my $button4 = $self->addButton(
            $grid,
            'Move down',
            'Move this flag down the priority list',
            undef,
            3, 6, 10, 11,
            TRUE,               # Irreversible
        );

        my $button5 = $self->addButton(
            $grid,
            'Move to top',
            'Move this flag down the priority list',
            undef,
            6, 9, 10, 11,
            TRUE,               # Irreversible
        );

        my $button6 = $self->addButton(
            $grid,
            'Move to bottom',
            'Move this flag down the priority list',
            undef,
            9, 12, 10, 11,
            TRUE,               # Irreversible
        );

        my $button7 = $self->addButton(
            $grid,
            'Sort by priority',
            'Sort room flags by priority',
            undef,
            1, 4, 11, 12,
        );

        my $button8 = $self->addButton(
            $grid,
            'Sort by filter',
            'Sort room flags by filter',
            undef,
            4, 8, 11, 12,
        );

        my $button11 = $self->addButton(
            $grid,
            'Sort alphabetically',
            'Sort room flags alphabetically',
            undef,
            8, 12, 11, 12,
        );

        my $button9 = $self->addButton(
            $grid,
            'Add custom flag',
            'Add a custom room flag',
            undef,
            1, 4, 12, 13,
        );

        my $button10 = $self->addButton(
            $grid,
            'Delete custom flag',
            'Delete a custom room flag',
            undef,
            4, 8, 12, 13,
        );
        $button10->set_sensitive(FALSE);


        my $button12 = $self->addButton(
            $grid,
            'Restore defaults',
            'Restore default flag colours/priorities',
            undef,
            8, 12, 12, 13,
            TRUE,               # Irreversible
        );

        # Signal connect - combobox
        $combo->signal_connect('changed' => sub {

            my $descrip = $combo->get_active_text();

            # (Set the IV immediately, so ->roomFlags1Tab_refreshList can use it)
            $self->editObj->set_roomFlagShowMode($descripHash{$descrip});

            # Reset widgets
            $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);
            $entry->set_text('');
            $entry2->set_text('');
            $entry3->set_text('');
            $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
            $canvasObj2 = $self->fillSimpleCanvas($canvas2, $canvasObj2, undef);
        });

        # Signal connect - 'Change' button
        $button->signal_connect('clicked' => sub {

            my ($posn, $name, $flagObj, $rgb);

            ($posn) = $slWidget->get_selected_indices();
            $name = $entry->get_text();
            if ($name) {

                $flagObj = $self->editObj->ivShow('roomFlagHash', $name);
            }

            if ($flagObj) {

                # Prompt the user to select a new colour
                $rgb = $self->showColourSelectionDialogue(
                    'Set \'' . $name . '\' flag colour',
                    $flagObj->colour,
                );
            }

            if ($rgb) {

                # Update the room flag object
                $flagObj->ivPoke('colour', $rgb);
                # Update widgets to show the change
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $rgb);
                $entry2->set_text($rgb);

                # Update the simple list
                $self->roomFlags1Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    $sortMode,
                );

                # Re-select the same line (for visual clarity)
                $slWidget->select($posn);
            }
        });

        # Signal connect - 'Use default' button
        $button2->signal_connect('clicked' => sub {

            my ($posn, $name, $flagObj, $tempFlagObj);

            ($posn) = $slWidget->get_selected_indices();
            $name = $entry->get_text();
            if ($name) {

                $flagObj = $self->editObj->ivShow('roomFlagHash', $name);
            }

            if ($flagObj) {

                # Ask the world model to create a temporary room flag object, containing default
                #   settings for this room flag
                $tempFlagObj = $self->editObj->getDefaultRoomFlag($self->session, $name);
            }

            if ($tempFlagObj) {

                # Update the room flag oject
                $flagObj->ivPoke('colour', $tempFlagObj->colour);
                # Update widgets to show the chaneg
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $tempFlagObj->colour);
                $entry2->set_text($tempFlagObj->colour);

                # Update the simple list
                $self->roomFlags1Tab_refreshList(
                    $slWidget,
                    scalar (@columnList / 2),
                    $sortMode,
                );

                # Re-select the same line (for visual clarity)
                $slWidget->select($posn);
            }
        });

        # Signal connect - 'Move up' button
        $button3->signal_connect('clicked' => sub {

            my ($posn, $name, $flagObj);

            ($posn) = $slWidget->get_selected_indices();
            $name = $entry->get_text();
            if ($name) {

                $flagObj = $self->editObj->ivShow('roomFlagHash', $name);
            }

            # Swap this room flag with the one just above it
            $self->editObj->moveRoomFlag($name, 'above');

            # Update the simple list
            $self->roomFlags1Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                $sortMode,
            );
            # Select the same line, in its new position (for visual clarity)
            $slWidget->select($flagObj->priority - 1);
        });

        # Signal connect - 'Move down' button
        $button4->signal_connect('clicked' => sub {

            my ($posn, $name, $flagObj);

            ($posn) = $slWidget->get_selected_indices();
            $name = $entry->get_text();
            if ($name) {

                $flagObj = $self->editObj->ivShow('roomFlagHash', $name);
            }

            # Swap this room flag with the one just above it
            $self->editObj->moveRoomFlag($name, 'below');

            # Update the simple list
            $self->roomFlags1Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                $sortMode,
            );
            # Select the same line, in its new position (for visual clarity)
            $slWidget->select($flagObj->priority - 1);
        });

        # Signal connect - 'Move to top' button
        $button5->signal_connect('clicked' => sub {

            my ($posn, $name, $flagObj);

            ($posn) = $slWidget->get_selected_indices();
            $name = $entry->get_text();
            if ($name) {

                $flagObj = $self->editObj->ivShow('roomFlagHash', $name);
            }

            # Swap this room flag with the one just above it
            $self->editObj->moveRoomFlag($name, 'top');

            # Update the simple list
            $self->roomFlags1Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                $sortMode,
            );
            # Select the same line, in its new position (for visual clarity)
            $slWidget->select($flagObj->priority - 1);
        });

        # Signal connect - 'Move to bottom' button
        $button6->signal_connect('clicked' => sub {

            my ($posn, $name, $flagObj);

            ($posn) = $slWidget->get_selected_indices();
            $name = $entry->get_text();
            if ($name) {

                $flagObj = $self->editObj->ivShow('roomFlagHash', $name);
            }

            # Swap this room flag with the one just above it
            $self->editObj->moveRoomFlag($name, 'bottom');

            # Update the simple list
            $self->roomFlags1Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                $sortMode,
            );
            # Select the same line, in its new position (for visual clarity)
            $slWidget->select($flagObj->priority - 1);
        });

        # Signal connect - 'Sort by priority' button
        $button7->signal_connect('clicked' => sub {

            $sortMode = 'priority';

            # Update the simple list
            $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);
        });

        # Signal connect - 'Sort by filter' button
        $button8->signal_connect('clicked' => sub {

            $sortMode = 'filter';

            # Update the simple list
            $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);
        });

        # Signal connect - 'Add custom flag' button
        $button9->signal_connect('clicked' => sub {

            # Prompt the user
            my ($name, $short, $descrip, $newCol) = $self->promptRoomFlag();

            if (defined $name && $name ne '') {

                if ($short eq '') {

                    $self->showMsgDialogue(
                        'Add custom flag',
                        'error',
                        'You must specify a short name, e.g. \'Ab\' or \'Xy\'',
                        'ok',
                    );

                } elsif ($descrip eq '') {

                    $self->showMsgDialogue(
                        'Add custom flag',
                        'error',
                        'You must specify a description (which is shown in the automapper'
                        . ' window\'s menus)',
                        'ok',
                    );

                } elsif ($self->session->worldModelObj->ivExists('roomFlagHash', $name)) {

                    $self->showMsgDialogue(
                        'Add custom flag',
                        'error',
                        'A room flag called \'' . $name . '\' already exists',
                        'ok',
                    );

                } else {

                    my $result = $self->editObj->addRoomFlag(
                        $self->session,
                        $name,
                        $short,
                        $descrip,
                        $newCol,
                    );
                }

                # Update the simple list
                $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);
            }
        });

        # Signal connect - 'Delete custom flag' button
        $button10->signal_connect('clicked' => sub {

            my ($posn, $name, $flagObj);

            # (Because this button is desensitised when not showing a custom flag, we don't need
            #   to check)
            $name = $entry->get_text();
            if ($name) {

                $self->editObj->deleteRoomFlag($name);
            }

            # Update the simple list
            $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);
        });

        # Signal connect - 'Sort alphabetically' button
        $button11->signal_connect('clicked' => sub {

            $sortMode = 'name';

            # Update the simple list
            $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);
        });

        # Signal connect - 'Restore defaults' button
        $button12->signal_connect('clicked' => sub {

            # Prompt for confirmation
            my $choice = $self->showMsgDialogue(
                'Restore defaults',
                'warning',
                'This operation will reset room flags back to their default state, eliminating'
                . ' any custom room flags you\'ve created. Are you sure you want to continue?',
                'yes-no',
            );

            if (defined $choice && $choice eq 'yes') {

                $self->editObj->resetRoomFlags($self->session);

                # Reset widgets
                $sortMode = 'priority';
                $self->roomFlags1Tab_refreshList($slWidget, scalar (@columnList / 2), $sortMode);
                $entry->set_text('');
                $entry2->set_text('');
                $entry3->set_text('');
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
                $canvasObj2 = $self->fillSimpleCanvas($canvas2, $canvasObj2, undef);
            }
        });

        # Make each row clickable
        $slWidget->signal_connect('cursor_changed' => sub {

            my ($name, $flagObj, $tempFlagObj);

            ($name) = $self->getSimpleListData($slWidget, 0);
            if ($name) {

                $flagObj = $self->editObj->ivShow('roomFlagHash', $name);
            }

            if ($flagObj) {

                # Ask the world model to create a temporary room flag object, containing default
                #   settings for this room flag
                # If no temporary object is returned, it's (probably) a custom flag with no default
                #   colour
                $tempFlagObj = $self->editObj->getDefaultRoomFlag($self->session, $name);

                # Update widgets
                $entry->set_text($name);
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $flagObj->colour);
                $entry2->set_text($flagObj->colour);

                if (! $tempFlagObj) {

                    $canvasObj2 = $self->fillSimpleCanvas($canvas2, $canvasObj2, undef);
                    $entry3->set_text('');
                    $button2->set_sensitive(FALSE);

                } else {

                    $canvasObj2 = $self->fillSimpleCanvas(
                        $canvas2,
                        $canvasObj2,
                        $tempFlagObj->colour,
                    );

                    $entry3->set_text($tempFlagObj->colour);
                    $button2->set_sensitive(TRUE);
                }
            }

            if (! $flagObj || $flagObj->filter ne 'custom') {
                $button10->set_sensitive(FALSE);
            } else {
                $button10->set_sensitive(TRUE);
            }
        });

        # Tab complete
        return 1;
    }

    sub roomFlags1Tab_refreshList {

        # Called by $self->roomFlags1Tab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #   $sortMode   - 'priority' - sort room flags by priority, 'filter' - sort by filter,
        #                   'name' - sort room flags by name
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $sortMode, $check) = @_;

        # Local variables
        my (
            @sortedList, @dataList,
            %ivHash, %showHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || ! defined $sortMode || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->roomFlags1Tab_refreshList',
                @_,
            );
        }

        # Import the hash of room flags (for speed)
        %ivHash = $self->editObj->roomFlagHash;

        # Sort them
        if ($sortMode eq 'priority') {

            @sortedList = sort {$a->priority <=> $b->priority} (values %ivHash);

        } elsif ($sortMode eq 'filter') {

            @sortedList = sort {
                if ($a->filter eq $b->filter) {
                    $a->priority <=> $b->priority;
                } else {
                    lc($a->filter) cmp lc($b->filter);
                }
            } (values %ivHash);

        } else {

            @sortedList = sort {lc($a->name) cmp lc($b->name)} (values %ivHash);
        }

        # Import a hash of room flags which should be visible in various 'edit' windows
        %showHash = $self->session->worldModelObj->getVisibleRoomFlags();

        # Compile the simple list data, eliminating any room flags which should be hidden
        foreach my $obj (@sortedList) {

            if (exists $showHash{$obj->name}) {

                push (@dataList,
                    $obj->name,
                    $obj->customFlag,
                    $obj->shortName,
                    $obj->priority,
                    $obj->filter,
                    $obj->colour,
                    $obj->descrip,
                );
            }
        }

        # Reset the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    sub roomFlags2Tab {

        # RoomFlags2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $check) = @_;

        # Local variables
        my (
            $roomFlag, $roomFlagObj, $colour, $noUpdateFlag,
            @columnList, @comboList, @comboList2, @comboList3,
        );

        # Check for improper arguments
        if (! defined $innerNotebook || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->roomFlags2Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _2',
            ['MSDP terrain types'],
        );

        # MSDP terrain types
        $self->addLabel($grid, '<b>MSDP terrain types</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of terrain types supplied by MSDP and their equivalent ' . $axmud::SCRIPT
            . ' room flags</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Terrain type', 'text',
            'Room flag', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, 'roomTerrainHash', \@columnList,
            1, 12, 2, 9);

        # Add editing widgets
        $self->addLabel($grid, 'Terrain type',
            1, 3, 9, 10);
        @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('roomTerrainInitHash'));
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            3, 6, 9, 10);

        $self->addLabel($grid, 'Room flag category (filter)',
            1, 4, 10, 11);
        @comboList2 = $axmud::CLIENT->constRoomFilterList;
        my $combo2 = $self->addComboBox($grid, undef, \@comboList2, '',
            TRUE,              # 'undef' value not used
            4, 6, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($grid, 'Room flag',
            6, 7, 10, 11);
        @comboList3 = $self->editObj->getRoomFlagsInFilter($combo2->get_active_text());
        my $combo3 = $self->addComboBox($grid, undef, \@comboList3, '',
            TRUE,              # 'undef' value not used
            7, 10, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($grid, 'Colour',
            10, 11, 10, 11);
        $roomFlag = $combo3->get_active_text();
        if ($roomFlag) {

            $roomFlagObj = $self->editObj->ivShow('roomFlagHash', $roomFlag);
            if ($roomFlagObj) {

                $colour = $roomFlagObj->colour;
            }
        }
        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($grid, $colour, undef,
            11, 12, 10, 11);

        # ->signal_connect from above
        $combo2->signal_connect('changed' => sub {

            my ($text2, $text3);

            $text2 = $combo2->get_active_text();

            # Don't let the ->signal_connect below react before we're ready
            $noUpdateFlag = TRUE;
            $self->resetComboBox(
                $combo3,
                $self->editObj->getRoomFlagsInFilter($text2),
            );

            $noUpdateFlag = FALSE;

            $text3 = $combo3->get_active_text();
            if ($text3) {
                $roomFlagObj = $self->editObj->ivShow('roomFlagHash', $text3);
            } else {
                $roomFlagObj = undef;
            }

            if ($roomFlagObj) {
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $roomFlagObj->colour);
            } else {
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
            }
        });

        $combo3->signal_connect('changed' => sub {

            my $text3 = $combo3->get_active_text();

            if (! $noUpdateFlag) {

                if ($text3) {
                    $roomFlagObj = $self->editObj->ivShow('roomFlagHash', $text3);
                } else {
                    $roomFlagObj = undef;
                }

                if ($roomFlagObj) {
                    $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $roomFlagObj->colour);
                } else {
                    $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
                }
            }
        });

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            'roomTerrainHash',
            11,
            $combo, $combo2, $combo3,
        );
        $button->signal_connect('clicked' => sub {

            my (
                $terrain, $roomFlag,
                %initHash, %modHash,
            );

            $terrain = $combo->get_active_text();
            $roomFlag = $combo3->get_active_text();
            %initHash = $self->getEditHash_hashIV('roomTerrainInitHash');
            %modHash = $self->getEditHash_hashIV('roomTerrainHash');

            # Update IVs
            if ($terrain && exists $initHash{$terrain} && $roomFlag) {

                delete $initHash{$terrain};
                $modHash{$terrain} = $roomFlag;

                $self->ivAdd('editHash', 'roomTerrainInitHash', \%initHash);
                $self->ivAdd('editHash', 'roomTerrainHash', \%modHash);

                # Update the simple list and reset combo boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'roomTerrainHash',
                );

                $self->resetComboBox($combo, sort {lc($a) cmp lc($b)} (keys %initHash));
            }
        });

        if (! @comboList) {

            # Can't add anything to the list if no terrain types have been received
            $button->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub roomFlags3Tab {

        # RoomFlags3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk3::Notebook object inside $self->notebook
        #   $number         - The page number to use for this tab
        #   $title, $descrip, $iv
        #                   - The title, description and IV to use for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $innerNotebook, $number, $title, $descrip, $iv, $check) = @_;

        # Local variables
        my (
            $roomFlag, $roomFlagObj, $colour, $noUpdateFlag,
            @columnList, @comboList, @comboList2,
        );

        # Check for improper arguments
        if (
            ! defined $innerNotebook || ! defined $number || ! defined $title || ! defined $descrip
            || ! defined $iv || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->roomFlags3Tab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $innerNotebook,
            'Page _' . $number,
            [$title],
        );

        # Painting by room titles
        $self->addLabel($grid, '<b>' . $title . '</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>' . $descrip . '</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Room flag', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, $iv, \@columnList,
            1, 12, 2, 9);

        # Add editing widgets
        $self->addLabel($grid, 'Pattern',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($grid, undef, 'regex', 1, undef,
            3, 12, 9, 10);

        $self->addLabel($grid, 'Room flag category (filter)',
            1, 4, 10, 11);
        @comboList = $axmud::CLIENT->constRoomFilterList;
        my $combo = $self->addComboBox($grid, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            4, 6, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($grid, 'Room flag',
            6, 7, 10, 11);
        @comboList2 = $self->editObj->getRoomFlagsInFilter($combo->get_active_text());
        my $combo2 = $self->addComboBox($grid, undef, \@comboList2, '',
            TRUE,              # 'undef' value not used
            7, 10, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($grid, 'Colour',
            10, 11, 10, 11);
        $roomFlag = $combo2->get_active_text();
        if ($roomFlag) {

            $roomFlagObj = $self->editObj->ivShow('roomFlagHash', $roomFlag);
            if ($roomFlagObj) {

                $colour = $roomFlagObj->colour;
            }
        }
        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($grid, $colour, undef,
            11, 12, 10, 11);

        # ->signal_connect from above
        $combo->signal_connect('changed' => sub {

            my ($text, $text2);

            $text = $combo->get_active_text();

            # Don't let the ->signal_connect below react before we're ready
            $noUpdateFlag = TRUE;
            $self->resetComboBox(
                $combo2,
                $self->editObj->getRoomFlagsInFilter($text),
            );

            $noUpdateFlag = FALSE;

            $text2 = $combo2->get_active_text();
            if ($text2) {
                $roomFlagObj = $self->editObj->ivShow('roomFlagHash', $text2);
            } else {
                $roomFlagObj = undef;
            }

            if ($roomFlagObj) {
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $roomFlagObj->colour);
            } else {
                $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
            }
        });

        $combo2->signal_connect('changed' => sub {

            my $text2 = $combo2->get_active_text();

            if (! $noUpdateFlag) {

                if ($text2) {
                    $roomFlagObj = $self->editObj->ivShow('roomFlagHash', $text2);
                } else {
                    $roomFlagObj = undef;
                }

                if ($roomFlagObj) {
                    $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, $roomFlagObj->colour);
                } else {
                    $canvasObj = $self->fillSimpleCanvas($canvas, $canvasObj, undef);
                }
            }
        });

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $grid,
            $slWidget,
            $iv,
            11,
            $entry, $combo,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $roomFlag);

            $pattern = $entry->get_text();
            $roomFlag = $combo2->get_active_text();

            if ($self->checkEntryIcon($entry)) {

                # Add a new key-value pair
                $self->modifyEditHash_hashIV($iv, $pattern, $roomFlag);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    $iv,
                );

                $self->resetEntryBoxes($entry);
            }
        });

        # Tab complete
        return 1;
    }

    sub lightTab {

        # Light tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @columnList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->lightTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            'L_ight',
            ['Light status list'],
        );

        # Light status list
        $self->addLabel($grid, '<b>Light status list</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $grid,
            '<i>List of light status values (always contains \'day\', \'night\' and'
            . ' \'darkness\')</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Current', 'bool',
            'Standard', 'bool',
            'Light status', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 10);

        # Initialise the simple list
        $self->lightTab_refreshList($slWidget, scalar (@columnList / 2));

        # Add entry boxes and buttons
        $self->addLabel($grid, 'Light status',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, 16,
            3, 6, 10, 11, 16, 16);
        my $button = $self->addButton($grid, 'Add', 'Add a new light status', undef,
            6, 9, 10, 11,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            my (
                $status, $matchFlag,
                @newList,
            );

            if ($self->checkEntryIcon($entry)) {

                $status = $entry->get_text();

                # Check that the list of light statuses doesn't already contain $status
                OUTER: foreach my $item ($self->editObj->lightStatusList) {

                    if ($item eq $status) {

                        $matchFlag = TRUE;
                        last OUTER;
                    }
                }

                if (! $matchFlag) {

                    # Add the new light status to the list
                    push (@newList, $status);
                }

                # Set the list of light statuses
                $self->session->pseudoCmd(
                    'setlightlist ' . join(' ' , @newList),
                    $self->pseudoCmdMode,
                );

                # Refresh the simple list and reset the entry box
                $self->lightTab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button2 = $self->addButton($grid,
            'Set', 'Set the selected light status as current', undef,
            9, 12, 10, 11,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            my ($status) = $self->getSimpleListData($slWidget, 2);
            if (defined $status) {

                # Set the current light status
                $self->session->pseudoCmd('setlightstatus ' . $status, $self->pseudoCmdMode);

                # Refresh the simple list and reset the entry box
                $self->lightTab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button3 = $self->addButton($grid, 'Delete', 'Delete the selected light status', undef,
            1, 3, 11, 12,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            my (
                $status,
                @newList,
            );

            ($status) = $self->getSimpleListData($slWidget, 2);
            if (defined $status) {

                # Remove the selected light status
                foreach my $item ($self->editObj->lightStatusList) {

                    if ($item ne $status) {

                        push (@newList, $item);
                    }
                }

                # Set the list of light statuses
                $self->session->pseudoCmd(
                    'setlightlist ' . join(' ' , @newList),
                    $self->pseudoCmdMode,
                );

                # Refresh the simple list and reset the entry box
                $self->lightTab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button4 = $self->addButton($grid,
            'Use defaults', 'Use the default list of light status values', undef,
            3, 6, 11, 12,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            # Use the default light of values
            $self->session->pseudoCmd('resetlightlist', $self->pseudoCmdMode);

            # Refresh the simple list and reset the entry box
            $self->lightTab_refreshList($slWidget, scalar (@columnList / 2));
            $self->resetEntryBoxes($entry);
        });

        my $button5 = $self->addButton(
            $grid,
            'Refresh list',
            'Refresh the list of light status values',
            undef,
            9, 12, 11, 12,
        );
        $button5->signal_connect('clicked' => sub {

            # Refresh the simple list and reset the entry box
            $self->lightTab_refreshList($slWidget, scalar (@columnList / 2));
            $self->resetEntryBoxes($entry);
        });

        # Tab complete
        return 1;
    }

    sub lightTab_refreshList {

        # Called by $self->lightTab to refresh the GA::Obj::SimpleList
        #
        # Expected arguments
        #   $slWidget   - The GA::Obj::SimpleList
        #   $columns    - The number of columns in the list
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $slWidget, $columns, $check) = @_;

        # Local variables
        my (
            @dataList,
            %standardHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->lightTab_refreshList', @_);
        }

        # Create a hash of standard light statuses, for quick checking
        foreach my $status ($self->editObj->constLightStatusList) {

            $standardHash{$status} = undef;
        }

        # Compile the simple list data
        foreach my $status ($self->editObj->lightStatusList) {

            if ($status eq $self->editObj->lightStatus) {
                push (@dataList, TRUE);
            } else {
                push (@dataList, FALSE);
            }

            if (exists $standardHash{$status}) {
                push (@dataList, TRUE, $status);
            } else {
                push (@dataList, FALSE, $status);
            }
        }

        # Refresh the simple list
        $self->resetListData($slWidget, [@dataList], $columns);

        return 1;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

{ package Games::Axmud::EditWin::Zonemap;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::Zonemap')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # We only need a single button so, instead of calling the generic ->enableButtons, call a
        #   method that creates just one button
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

        my ($self, $hBox, $check) = @_;

        # Local variables
        my @emptyList;

        # Check for improper arguments
        if (! defined $hBox || defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableButtons', @_);
            return @emptyList;
        }

        return $self->enableSingleButton($hBox);
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->winEnable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup, using the standard grid size
        my $grid = $self->addTab(
            $self->notebook,
            '_Name',
            ['Zonemap settings'],
        );

        # Zonemap 'edit' windows have some non-standard IVs used to store the widgets created in
        #   $self->zoneModelsTab, so that several ->zoneModelsTab_XXX functions can retrieve them
        #   easily
        # The values for each IV are set in ->zoneModelsTab
        $self->{borderX} = undef;
        $self->{borderY} = undef;
        $self->{gridSize} = undef;
        $self->{cellWidth} = undef;
        $self->{cellHeight} = undef;
        $self->{clickMode} = undef;

        $self->{canvasWidget} = undef;
        $self->{bgCanvasObj} = undef;
        $self->{gridCanvasObj} = undef;
        $self->{gridCanvasObj2} = undef;
        $self->{selectCanvasObj} = undef;
        $self->{drawnObjList} = [];

        $self->{entryStartX} = undef;
        $self->{entryStartY} = undef;
        $self->{entryStopX} = undef;
        $self->{entryStopY} = undef;
        $self->{addZoneCombo} = undef;
        $self->{addZoneButton} = undef;
        $self->{cancelButton} = undef;
        $self->{mouseStep} = undef;
        $self->{startX} = undef;
        $self->{startY} = undef;
        $self->{stopX} = undef;
        $self->{stopY} = undef;

        $self->{entryCurrentZone} = undef;
        $self->{entryCurrentTable} = undef;
        $self->{frame} = undef;
        $self->{simpleCanvasWidget} = undef;
        $self->{simpleCanvasObj} = undef;
        $self->{simpleCanvasSize} = 30;
        $self->{frame} = undef;
        $self->{editButton} = undef;
        $self->{deleteButton} = undef;

        # Set up the rest of the tab
        $self->nameTab($grid);

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->zoneModelsTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $grid   -  The Gtk3::Grid which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $grid, $check) = @_;

        # Check for improper arguments
        if (! defined $grid || defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab', @_);
        }

        # Tab setup (already created by the calling function)

        # Left column
        $self->addLabel($grid, '<b>Zonemap settings</b>',
            0, 6, 0, 2);
        $self->addLabel($grid, 'Zonemap name',
            1, 3, 2, 4);
        $self->addEntry($grid, 'name', FALSE,
            3, 6, 2, 4, 16, 16);

        # Right column
        $self->addCheckButton($grid, 'Zonemap is full', 'fullFlag', FALSE,
            7, 12, 2, 4);
        $self->addCheckButton($grid, 'Zonemap is temporary', 'tempFlag', FALSE,
            7, 12, 4, 6);

        # Tab complete
        return 1;
    }

    sub zoneModelsTab {

        # ZoneModels tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $widthRequest, $heightRequest,
            @comboList,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->zoneModelsTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Zone models',
            ['Add a new zone model', 'Zone model actions'],
        );

        # We're going to use a GooCanvas2::Canvas to draw a grid containing squares in various
        #   colours

        ########
        # Part 1 - set up the canvas, and draw an empty grid

        # Unusual step - we have to save a number of variables as IVs, so that this object's methods
        #   can access them, without passing a million arguments back and forth
        $self->ivPoke('borderX', 10);           # Size of area outside the grid (pixels)
        $self->ivPoke('borderY', 10);           #
        $self->ivPoke('gridSize', 60);          # Grid is 60x60 gridblocks
        $self->ivPoke('cellWidth', 6);          # Size of each gridblock (pixels)
        $self->ivPoke('cellHeight', 5);         #

        # Add a drawing canvas, displaying a 60x60 grid containing any number of zones
        #   $self->zoneModelsTab_getMouseClick  - Responds to mouse clicks on the canvas
        #   $self->zoneModelsTab_getMouseMotion - Responds to mouse motion over the canvas
        $widthRequest = ($self->borderX * 2) + ($self->gridSize * $self->cellWidth);
        $heightRequest = ($self->borderY * 2) + ($self->gridSize * $self->cellHeight);

        my $canvasWidget = $self->addDrawingCanvas(
            $grid,
            'zoneModelsTab_getMouseClick',
            'zoneModelsTab_getMouseMotion',
            FALSE, FALSE,                       # No scrolling
            0, 7, 0, 12,                        # Position in grid
            $widthRequest, $heightRequest,      # Requested size of viewport
        );

        # Draw the canvas background (in white)
        my $canvasObj = GooCanvas2::CanvasRect->new(
            'parent' => $canvasWidget->get_root_item(),
            x => 0,
            y => 0,
            width => $widthRequest,
            height => $heightRequest,
#            'line-width' => 2,
            'stroke-color' => '#FFFFFF',
            'fill-color' => '#FFFFFF',
        );

        $canvasObj->lower();

        # Draw a 60x60 grid on the canvas
        my $canvasObj2 = GooCanvas2::CanvasGrid->new(
            'parent' => $canvasWidget->get_root_item(),
            x => $self->borderX,
            y => $self->borderY,
            width => ($self->gridSize * $self->cellWidth),
            height => ($self->gridSize * $self->cellHeight),
            x_step => $self->cellWidth,
            y_step => $self->cellHeight,
            x_offset => 0,
            y_offset => 0,
            'horz-grid-line-width' => 1,
            'horz-grid-line-color' => '#808080',
            'vert-grid-line-width' => 1,
            'vert-grid-line-color' => '#808080',
        );

        # Overlay that with a 12x12 grid in a stronger colour, to clearly show the subvidisions
        my $canvasObj3 = GooCanvas2::CanvasGrid->new(
            'parent' => $canvasWidget->get_root_item(),
            x => $self->borderX,
            y => $self->borderY,
            width => ($self->gridSize * $self->cellWidth),
            height => ($self->gridSize * $self->cellHeight),
            x_step => ($self->cellWidth * 5),
            y_step => ($self->cellHeight * 5),
            x_offset => 0,
            y_offset => 0,
            'horz-grid-line-width' => 1,
            'horz-grid-line-color' => '#000000',
            'vert-grid-line-width' => 1,
            'vert-grid-line-color' => '#000000',
        );

        $canvasObj3->raise();

        # Save more IVs
        $self->ivPoke('canvasWidget', $canvasWidget);
        $self->ivPoke('bgCanvasObj', $canvasObj);
        $self->ivPoke('gridCanvasObj', $canvasObj2);
        $self->ivPoke('gridCanvasObj2', $canvasObj3);

        # Now draw any existing zone models
        $self->zoneModelsTab_refreshGrid();

        ########
        # Part 2 - widgets for adding new zones

        $self->addLabel($grid, '<b>Add a new zone model</b>',
            7, 12, 0, 1);

        # Create an IV to store whether we want 1-block clicks or 5-block clicks
        $self->ivPoke('clickMode', 1);  # 0     - 1-block clicks
                                        # 1     - 5-block clicks

        my ($radioGroup, $radioButton, $radioButton2);
        ($radioGroup, $radioButton) = $self->addRadioButton($grid,
            undef, 'Use 5-block clicks', undef, undef, TRUE,
            7, 12, 1, 2);

        ($radioGroup, $radioButton2) = $self->addRadioButton($grid,
            $radioGroup, 'Use 1-block clicks', undef, undef, TRUE,
            7, 12, 2, 3);

        $self->addLabel($grid, 'Top-left corner',
            7, 9, 3, 4);
        my $entry = $self->addEntry($grid, undef, FALSE,
            9, 10, 3, 4, 3, 3);
        my $entry2 = $self->addEntry($grid, undef, FALSE,
            10, 12, 3, 4, 3, 3);

        $self->addLabel($grid, 'Bottom-right corner',
            7, 9, 4, 5);
        my $entry3 = $self->addEntry($grid, undef, FALSE,
            9, 10, 4, 5, 3, 3);
        my $entry4 = $self->addEntry($grid, undef, FALSE,
            10, 12, 4, 5, 3, 3);

        # Save these entry boxes as IVs, so they can be updated as the mouse moves over the drawing
        #   canvas
        $self->ivPoke('entryStartX', $entry);
        $self->ivPoke('entryStartY', $entry2);
        $self->ivPoke('entryStopX', $entry3);
        $self->ivPoke('entryStopY', $entry4);

        # New zones are selected in three steps - click on the top-left corner of the zone, then on
        #   the bottom-right, then on the 'add zone model' button
        my $button = $self->addButton($grid,
            'Add zone model', 'Create a zone model using the selected area', undef,
            7, 9, 5, 6,
            TRUE,           # Irreversible
        );
        my $button2 = $self->addButton($grid,
            'Cancel', 'Don\'t create a zone model using the selected area', undef,
            9, 12, 5, 6);

        # Save the buttons as IVs, so that they can be sensitised/desensitised (start desensitised)
        $self->ivPoke('addZoneButton', $button);
        $self->ivPoke('cancelButton', $button2);
        $button->set_sensitive(FALSE);
        $button2->set_sensitive(FALSE);

        # We also have to save which step we're on as an IV, so that other functions can use it
        $self->ivPoke('mouseStep', 1);
        # On steps 1 and 2, we save the location of the mouse clicks
        $self->ivPoke('startX', undef);
        $self->ivPoke('startY', undef);
        $self->ivPoke('stopX', undef);
        $self->ivPoke('stopY', undef);

        ########
        # Part 3 - widgets for existing zones

        $self->addLabel($grid, '<b>Zone model actions</b>',
            7, 12, 6, 7);

        # When clicking on an existing zone, the zone model's number is displayed...
        $self->addLabel($grid, 'Selected model #',
            7, 9, 7, 8);
        my $entry5 = $self->addEntry($grid, undef, FALSE,
            9, 10, 7, 8, 3, 3);
        $self->ivPoke('entryCurrentZone', $entry5);

        # ...as is its colour
        my ($frame, $simpleCanvasWidget, $simpleCanvasObj) = $self->addSimpleCanvas(
            $grid, undef, undef,
            10, 11, 7, 8,               # Position in grid
            $self->simpleCanvasSize,    # Width request
            $self->simpleCanvasSize,    # Height request
        );
        $self->ivPoke('frame', $frame);
        $self->ivPoke('simpleCanvasWidget', $simpleCanvasWidget);
        $self->ivPoke('simpleCanvasObj', $simpleCanvasObj);

        # (This empty label reduces the size of the simple canvas, so that the whole frame is filled
        #   by a single colour)
        $self->addLabel($grid, '',
            11, 12, 7, 8);

        # Other widgets
        my $button3;
        if ($axmud::CLIENT->checkCurrentZonemap($self->editObj->name)) {

            $button3 = $self->addButton($grid, 'View...', 'View the selected zone model', undef,
                7, 9, 8, 9);

        } else {

            $button3 = $self->addButton($grid, 'Edit...', 'Edit the selected zone model', undef,
                7, 9, 8, 9);
        }

        my $button4 = $self->addButton($grid, 'Delete', 'Delete the selected zone model', undef,
            9, 12, 8, 9,
            TRUE,           # Irreversible
        );
        my $button5 = $self->addButton($grid,
            'Dump list', 'Display the list of zone models in the \'main\' window', undef,
            7, 9, 9, 10);
        my $button6 = $self->addButton($grid, 'Refresh grid', 'Refresh the zonemap grid', undef,
            9, 12, 9, 10);

        # Save the buttons as IVs, so that it can be sensitised/desensitised (starts desensitised.
        #   Don't need to save or desensitise the 'dump' button)
        $self->ivPoke('editButton', $button3);
        $self->ivPoke('deleteButton', $button4);
        $button3->set_sensitive(FALSE);
        $button4->set_sensitive(FALSE);

        ########
        # Part 4 - signal connects

        # Add zone model button
        $button->signal_connect('clicked' => sub {

            # Add the new zone model
            $self->session->pseudoCmd(
                'addzonemodel ' . $self->editObj->name
                . ' -p ' . $self->startX . ' ' . $self->startY
                . ' -s ' . ($self->stopX - $self->startX + 1)
                . ' ' . ($self->stopY - $self->startY + 1),
                $self->pseudoCmdMode,
            );

            # We don't know which zone model was created, so re-draw all of them
            # Re-draw the grid and all existing zone models, and return to step 1
            $self->zoneModelsTab_refreshGrid();
            $self->zoneModelsTab_resetMouseStep();

        });

        # Cancel button
        $button2->signal_connect('clicked' => sub {

            # Re-draw the grid and all existing zones, and return to step 1
            $self->zoneModelsTab_resetMouseStep();
        });

        # Edit/View button
        $button3->signal_connect('clicked' => sub {

            my ($obj, $number);

            # Edit the selected zone model
            if ($self->entryCurrentZone) {

                $number = $self->entryCurrentZone->get_text();
                if (defined $number) {

                    # Get the blessed reference of the zone model
                    $obj = $self->editObj->ivShow('modelHash', $number);
                    if ($obj) {

                        # Open an 'edit' window for the zone model
                        $self->createFreeWin(
                            'Games::Axmud::EditWin::ZoneModel',
                            $self,
                            $self->session,
                             'Edit zone model #' . $number,
                            $obj,
                            FALSE,                          # Not temporary
                        );
                    }
                }
            }

            # Re-draw the grid and all existing zone models, and return to step 1
            $self->zoneModelsTab_refreshGrid();
            $self->zoneModelsTab_resetMouseStep();
        });

        # Delete button
        $button4->signal_connect('clicked' => sub {

            my $number;

            # Delete the selected zone model
            if ($self->entryCurrentZone) {

                $number = $self->entryCurrentZone->get_text();
                if (defined $number) {

                    $self->session->pseudoCmd(
                        'deletezonemodel ' . $self->editObj->name . ' ' . $number,
                        $self->pseudoCmdMode,
                    );

                    # Re-draw the grid and all existing zone models, and return to step 1
                    $self->zoneModelsTab_refreshGrid();
                    $self->zoneModelsTab_resetMouseStep();
                }
            }
        });

        # Dump list button
        $button5->signal_connect('clicked' => sub {

            $self->session->pseudoCmd(
                'listzonemodel ' . $self->editObj->name,
                $self->pseudoCmdMode,
            );
        });

        # Refresh grid button
        $button6->signal_connect('clicked' => sub {

            # Re-draw the grid and all existing zones, and return to step 1
            $self->zoneModelsTab_resetMouseStep();
        });

        # 5-block / 1-block clicks radio button
        $radioButton->signal_connect('toggled' => sub {

            # Re-draw the grid and all existing zones (including the new one, if it was created),
            #   and return to mouse step 1
            $self->zoneModelsTab_resetMouseStep();

            if ($radioButton->get_active()) {
                $self->ivPoke('clickMode', 1);
            } else {
                $self->ivPoke('clickMode', 0);
            }
        });

        # For temporary zonemaps, no widgets on this tab are sensitive (including the 'edit' button)
        if ($self->editObj->tempFlag) {

            $radioButton->set_sensitive(FALSE);
            $radioButton2->set_sensitive(FALSE);
            $button4->set_sensitive(FALSE);
            $button5->set_sensitive(FALSE);
            $button6->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub zoneModelsTab_resetMouseStep {

        # Called by $self->zoneModelsTab when the $self->mouseStep needs to be set back to 1, either
        #   because the user clicked a third time on the drawing canvas, or because they clicked the
        #   'cancel' button, or because they changed from 1-block clicks to 5-block clicks (or
        #   vice-versa)
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my $canvasObj;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->zoneModelsTab_resetMouseStep',
                @_,
            );
        }

        # Remove the selected block/zone, if drawn
        if ($self->selectCanvasObj) {

            $self->selectCanvasObj->remove();
            $self->ivUndef('selectCanvasObj');
        }

        # Go back to step 1
        $self->ivPoke('mouseStep', 1);
        $self->ivUndef('startX');
        $self->ivUndef('stopX');
        $self->ivUndef('startY');
        $self->ivUndef('stopY');

        # Desensitise the 'Add zone model'/'Cancel'/'Edit'/'Delete' buttons
        $self->addZoneButton->set_sensitive(FALSE);
        $self->cancelButton->set_sensitive(FALSE);
        $self->editButton->set_sensitive(FALSE);
        $self->deleteButton->set_sensitive(FALSE);

        # Reset the entry boxes
        $self->entryStartX->set_text('');
        $self->entryStartY->set_text('');
        $self->entryStopX->set_text('');
        $self->entryStopY->set_text('');
        $self->entryCurrentZone->set_text('');

        # Reset the colour displayed in the simple canvas
        $canvasObj = $self->fillSimpleCanvas(
            $self->simpleCanvasWidget,
            $self->simpleCanvasObj,
            '#FFFFFF',
            $self->simpleCanvasSize,
            $self->simpleCanvasSize,
        );

        $self->ivPoke('simpleCanvasObj', $canvasObj);

        # Move back to the first step
        $self->ivPoke('mouseStep', 1);

        return 1;
    }

    sub zoneModelsTab_getMouseClick {

        # Called by ->signal_connect in $self->addDrawingCanvas (inherited from GA::Generic::Win),
        #   whenever the user clicks on the drawing canvas
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $xPos, $yPos    - The coordinates on the drawing canvas at which the user clicked
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $xPos, $yPos, $check) = @_;

        # Local variables
        my (
            $xGrid, $yGrid, $zoneModelObj, $number, $posn, $canvasObj, $canvasObj2,
            @colourList, @sortedList,
        );

        # Check for improper arguments
        if (! defined $xPos || ! defined $yPos || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->zoneModelsTab_getMouseClick',
                @_,
            );
        }

        # Convert $xPos and $yPos - coordinates in the drawing canvas - into coordinates on the grid
        $xGrid = int( ($xPos - $self->borderX) / $self->cellWidth);
        $yGrid = int( ($yPos - $self->borderY) / $self->cellHeight);

        # Import a list of 32 colours in roughly rainbow order
        @colourList = $axmud::CLIENT->constRainbowColourList;

        # Ignore clicks that are outside the grid (e.g. the lines at the bottom right will give the
        #   values of $xGrid/$yGrid = 60 - we must ignore them)
        if (
            $xPos >= $self->borderX && $yPos >= $self->borderY
            && $xGrid >= 0 && $xGrid < $self->gridSize
            && $yGrid >= 0 && $yGrid < $self->gridSize
        ) {
            # See if this particular grid block is occupied; if so, display the number of the zone
            #   model that occupies it
            $zoneModelObj = $self->editObj->findZoneModel($xGrid, $yGrid, 1, 1);
            if ($zoneModelObj) {

                # Refresh the grid, and return to mouse step 1
                $self->zoneModelsTab_resetMouseStep();

                # Display the number and colour of the clicked zone in one of the entry boxes
                $number = $zoneModelObj->number;

                $self->entryCurrentZone->set_text($number);

                # Zone models can be deleted, leaving a list of zone models numbered something like
                #   (0, 1, 2, 4)
                # Find the position in the list of the selected zone model, so we can assign the
                #   correct colour to it
                @sortedList = sort {$a <=> $b} ($self->editObj->ivKeys('modelHash'));
                OUTER: for (my $count = 0; $count < scalar @sortedList; $count++) {

                    if ($sortedList[$count] == $number) {

                        $posn = $count;
                        last OUTER;
                    }
                }

                # Emergency fallback
                if (! defined $posn) {

                    $posn = 0;
                }

                # There are 32 colours in GA::Client->constRainbowColourList, after which we go back
                #   to the beginning of the list, and continue using colours. If $posn is more than
                #   31, work out which colour is being used
                if ($posn >= (scalar @colourList)) {

                    $posn = $posn % (scalar @colourList);
                }

                # Set the colour displayed in the simple canvas
                $canvasObj = $self->fillSimpleCanvas(
                    $self->simpleCanvasWidget,
                    $self->simpleCanvasObj,
                    $colourList[$posn],
                    $self->simpleCanvasSize,
                    $self->simpleCanvasSize,
                );

                $self->ivPoke('simpleCanvasObj', $canvasObj);

                # Display the grid coordinates of the clicked block
                $self->entryStartX->set_text($zoneModelObj->left);
                $self->entryStartY->set_text($zoneModelObj->top);
                $self->entryStopX->set_text($zoneModelObj->right);
                $self->entryStopY->set_text($zoneModelObj->bottom);

                # Allow the 'Edit'/'Delete' buttons to be clicked now
                $self->editButton->set_sensitive(TRUE);
                if (! $self->editObj->tempFlag) {

                    $self->deleteButton->set_sensitive(TRUE);
                }

                return 1;

            } elsif ($self->editObj->tempFlag) {

                # This 'edit' window can't be used to create new zonemodels for temporary zonemaps.
                #   In case the previous click was on an existing zonemodel, reset widgets
                $self->zoneModelsTab_resetMouseStep();

                return 1;

            } elsif ($self->mouseStep == 1) {

                if ($self->clickMode) {

                    # 5-block clicks. Adjust $xGrid and $yGrid to the top-left corner of a group of
                    #   5x5 blocks
                    $xGrid = (int ($xGrid / 5) * 5);
                    $yGrid = (int ($yGrid / 5) * 5);
                }

                # Freeze these coordinates as the top-left corner of the new zone
                $self->ivPoke('startX', $xGrid);
                $self->ivPoke('startY', $yGrid);

                $self->entryStartX->set_text($xGrid);
                $self->entryStartY->set_text($yGrid);

                if (! $self->clickMode) {

                    # Mark the gridblock pink, as a visual aid
                    $self->zoneModelsTab_fillBlock(
                        '#FFC8CB',
                        $xGrid,
                        $yGrid,
                        TRUE,               # Don't add this to $self->drawnObjList
                    );

                } else {

                    # Mark a 5x5 group of gridblocks pink
                    $self->zoneModelsTab_fillZone(
                        '#FFC8CB',
                        $self->startX,
                        $self->startY,
                        $self->startX + 4,
                        $self->startY + 4,
                        TRUE,               # Don't add this to $self->drawnObjList
                    );
                }

                $self->ivPoke('selectCanvasObj', $canvasObj);

                # If the number of a zone that's been clicked on is displayed, remove it
                $self->entryCurrentZone->set_text('');

                # Reset the colour displayed in the simple canvas
                $canvasObj2 = $self->fillSimpleCanvas(
                    $self->simpleCanvasWidget,
                    $self->simpleCanvasObj,
                    '#FFFFFF',
                    $self->simpleCanvasSize,
                    $self->simpleCanvasSize,
                );

                $self->ivPoke('simpleCanvasObj', $canvasObj2);

                # Desensitise the 'Edit'/'Delete' buttons
                $self->editButton->set_sensitive(FALSE);
                $self->deleteButton->set_sensitive(FALSE);

                # Move on to the next step
                $self->ivPoke('mouseStep', 2);

            } elsif ($self->mouseStep == 2) {

                my ($newStartX, $newStartY, $newStopX, $newStopY);

                if ($self->clickMode) {

                    # 5-block clicks. Adjust $xGrid and $yGrid to the top-left corner of a group of
                    #   5x5 blocks
                    $xGrid = (int ($xGrid / 5) * 5);
                    $yGrid = (int ($yGrid / 5) * 5);
                }

                # We may have to invert the two points, so that one is top-left, and the other is
                #   top-right

                # A....
                # ....B
                if ($xGrid >= $self->startX && $yGrid >= $self->startY) {

                    # (No inversion needed)
                    $newStartX = $self->startX;
                    $newStartY = $self->startY;
                    $newStopX = $xGrid;
                    $newStopY = $yGrid;

                # ....A
                # B....
                } elsif ($xGrid < $self->startX && $yGrid >= $self->startY) {

                    $newStartX = $xGrid;
                    $newStartY = $self->startY;
                    $newStopX = $self->startX;
                    $newStopY = $yGrid;

                # ....B
                # A....
                } elsif ($xGrid >= $self->startX && $yGrid < $self->startY) {

                    $newStartX = $self->startX;
                    $newStartY = $yGrid;
                    $newStopX = $xGrid;
                    $newStopY = $self->startY;

                # B....
                # ....A
                } else {

                    $newStartX = $xGrid;
                    $newStartY = $yGrid;
                    $newStopX = $self->startX;
                    $newStopY = $self->startY;
                }

                # If we are doing blocks of 5, the bottom-right click (which might be A or B, but
                #   which is now $newStopX and $newStopY) is at the top-left of a block of 5.
                #   Adjust the coordinates so they are at the bottom-right of the block of 5.
                if ($self->clickMode) {

                    $newStopX += 4;
                    $newStopY += 4;
                }

                # Freeze these coordinates as the top-left and bottom-right corners of the zone
                $self->ivPoke('startX', $newStartX);
                $self->ivPoke('startY', $newStartY);
                $self->ivPoke('stopX', $newStopX);
                $self->ivPoke('stopY', $newStopY);

                $self->entryStartX->set_text($newStartX);
                $self->entryStartY->set_text($newStartY);
                $self->entryStopX->set_text($newStopX);
                $self->entryStopY->set_text($newStopY);

                # Paint the potentional zone pink, as a visual aid
                $self->zoneModelsTab_fillZone(
                    '#FFC8CB',
                    $self->startX,
                    $self->startY,
                    $self->stopX,
                    $self->stopY,
                    TRUE,               # Don't add this to $self->drawnObjList
                );

                $self->ivPoke('selectCanvasObj', $canvasObj);

                # The 'Add zone model'/'Cancel' buttons are now sensitised
                $self->addZoneButton->set_sensitive(TRUE);
                $self->cancelButton->set_sensitive(TRUE);

                # Move on to the next step
                $self->ivPoke('mouseStep', 3);

            } elsif ($self->mouseStep == 3) {

                # Refresh the grid, and return to mouse step 1
                $self->zoneModelsTab_resetMouseStep();
            }
        }

        return 1;
    }

    sub zoneModelsTab_getMouseMotion {

        # Called by ->signal_connect in $self->addDrawingCanvas (inherited from GA::Generic::Win),
        #   whenever the user moves the mouse pointer over the drawing canvas
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $xPos, $yPos    - The current coordiantes of the mouse over the drawing canvas
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $xPos, $yPos, $check) = @_;

        # Local variables
        my ($xGrid, $yGrid);

        # Check for improper arguments
        if (! defined $xPos || ! defined $yPos || defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->zoneModelsTab_getMouseMotion',
                @_,
            );
        }

        # Convert $xPos and $yPos - coordinates in the drawing canvas - into coordinates on the grid
        $xGrid = int( ($xPos - $self->borderX) / $self->cellWidth);
        $yGrid = int( ($yPos - $self->borderY) / $self->cellHeight);

        # Ignore positions that are outside the grid
        if ($xGrid >= 0 && $xGrid <= $self->gridSize && $yGrid >= 0 && $yGrid <= $self->gridSize) {

            # Update the entry boxes, or not, depending on which mouse step we're at
            if ($self->mouseStep == 1) {

                # Update all four entry boxes
                $self->entryStartX->set_text($xGrid);
                $self->entryStartY->set_text($yGrid);
                $self->entryStopX->set_text($xGrid);
                $self->entryStopY->set_text($yGrid);

            } elsif ($self->mouseStep == 2) {

                # The first two entry boxes are fixed
                $self->entryStopX->set_text($xGrid);
                $self->entryStopY->set_text($yGrid);
            }
        }

        return 1;
    }

    sub zoneModelsTab_refreshGrid {

        # Called by ->zoneModelsTab
        # Draws (or re-draws) all existing zone models
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my @colourList;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->zoneModelsTab_refreshGrid',
                @_,
            );
        }

        # Remove any existing zone models
        foreach my $canvasObj ($self->drawnObjList) {

            $canvasObj->remove();
        }

        # Import a list of 32 colours in roughly rainbow order
        @colourList = $axmud::CLIENT->constRainbowColourList;
        # Draw each zone model in turn, using a different colour for each. If we run out of colours,
        #   start again from the first colour
        foreach my $modelNum (sort {$a <=> $b} ($self->editObj->ivKeys('modelHash'))) {

            my ($modelObj, $colour);

            $modelObj = $self->editObj->ivShow('modelHash', $modelNum);

            $colour = shift @colourList;
            if (! $colour) {

                # All of the colours have been used - so re-import the list, and start taking
                #   colours from the beginning again
                @colourList = $axmud::CLIENT->constRainbowColourList;
                $colour = shift @colourList;
            }

            $self->zoneModelsTab_fillZone(
                $colour,
                $modelObj->left,
                $modelObj->top,
                $modelObj->right,
                $modelObj->bottom,
            );
        }

        # Operation complete
        return 1;
    }

    sub zoneModelsTab_fillBlock {

        # Called by $self->zoneModelsTab_getMouseClick
        # Fills in a single block of the grid in a single colour
        #
        # Expected arguments
        #   $colour         - The colour (an RGB tag, e.g. '#ABCDEF')
        #   $xPos, $yPos    - The coordinates of the gridblock to be filled in
        #
        # Optional arguments
        #   $tempFlag       - If TRUE, it's a temporary (pink) area of the grid that should not be
        #                       added to $self->drawnObjList. FALSE (or 'undef') otherwise
        #
        # Return values
        #   'undef' on improper arguments
        #   The drawn canvas object otherwise

        my ($self, $colour, $xPos, $yPos, $tempFlag, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $colour || ! defined $xPos || ! defined $yPos || ! defined $colour
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->zoneModelsTab_fillBlock',
                @_,
            );
        }

        # (Drawing any coloured area displaces any selection that has been made)
        if ($self->selectCanvasObj) {

            $self->selectCanvasObj->remove();
            $self->ivUndef('selectCanvasObj');
        }

        # Fill the gridblock at grid coordinates $xPos, $yPos
        my $canvasObj = GooCanvas2::CanvasRect->new(
            'parent' => $self->canvasWidget->get_root_item(),
            x => $self->borderX + ($xPos * $self->cellWidth),
            y => $self->borderY + ($yPos * $self->cellHeight),
            width => $self->cellWidth,
            height => $self->cellHeight,
            'line-width' => 1,
            'stroke-color' => $colour,
            'fill-color' => $colour,
        );

        $canvasObj->lower($self->gridCanvasObj);
        if (! $tempFlag) {

            $self->ivPush('drawnObjList', $canvasObj);
        }

        return $canvasObj;
    }

    sub zoneModelsTab_fillZone {

        # Called by $self->zoneModelsTab_getMouseClick and ->zoneModelsTab_refreshGrid
        # Fills in an area of the grid in a single colour, representing a particular zone model
        #
        # Expected arguments
        #   $colour         - The colour (an RGB tag, e.g. '#ABCDEF')
        #   $startX, $startY, $stopX, $stopY
        #                   - The area of the grid to fill in
        #
        # Optional arguments
        #   $tempFlag       - If TRUE, it's a temporary (pink) area of the grid that should not be
        #                       added to $self->drawnObjList. FALSE (or 'undef') otherwise
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $colour, $startX, $startY, $stopX, $stopY, $tempFlag, $check) = @_;

        # Local variables
        my ($x, $y, $width, $height);

        # Check for improper arguments
        if (
            ! defined $colour || ! defined $startX || ! defined $startY || ! defined $stopX
            || ! defined $stopY || ! defined $colour || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->zoneModelsTab_fillZone', @_);
        }

        # (Drawing any coloured area displaces any selection that has been made)
        if ($self->selectCanvasObj) {

            $self->selectCanvasObj->remove();
            $self->ivUndef('selectCanvasObj');
        }

        # For reasons unknown, the coloured zone seeps outside the top and left boundaries of the
        #   grid. Adjust the zone's size and position to prevent that
        $x = $self->borderX + ($startX * $self->cellWidth);
        $y = $self->borderY + ($startY * $self->cellHeight);
        $width = ($self->cellWidth * ($stopX - $startX + 1));
        $height = ($self->cellHeight * ($stopY - $startY + 1));
        if ($x <= $self->borderX) {

            $x++;
            $width--;
        }

        if ($y <= $self->borderY) {

            $y++;
            $height--;
        }

        my $canvasObj = GooCanvas2::CanvasRect->new(
            'parent' => $self->canvasWidget->get_root_item(),
            x => $x,
            y => $y,
            width => $width,
            height => $height,
            'line-width' => 1,
            'stroke-color' => $colour,
            'fill-color' => $colour,
        );

        $canvasObj->lower($self->gridCanvasObj);
        if (! $tempFlag) {

            $self->ivPush('drawnObjList', $canvasObj);
        }

        return $canvasObj;
    }

    ##################
    # Accessors - set

    ##################
    # Accessors - get

    # (Non-standard IVs for this 'edit' window)
    sub borderX
        { $_[0]->{borderX} }
    sub borderY
        { $_[0]->{borderY} }
    sub gridSize
        { $_[0]->{gridSize} }
    sub cellWidth
        { $_[0]->{cellWidth} }
    sub cellHeight
        { $_[0]->{cellHeight} }
    sub clickMode
        { $_[0]->{clickMode} }

    sub canvasWidget
        { $_[0]->{canvasWidget} }
    sub bgCanvasObj
        { $_[0]->{bgCanvasObj} }
    sub gridCanvasObj
        { $_[0]->{gridCanvasObj} }
    sub gridCanvasObj2
        { $_[0]->{gridCanvasObj2} }
    sub selectCanvasObj
        { $_[0]->{selectCanvasObj} }
    sub drawnObjList
        { my $self = shift; return @{$self->{drawnObjList}}; }

    sub entryStartX
        { $_[0]->{entryStartX} }
    sub entryStartY
        { $_[0]->{entryStartY} }
    sub entryStopX
        { $_[0]->{entryStopX} }
    sub entryStopY
        { $_[0]->{entryStopY} }
    sub addZoneButton
        { $_[0]->{addZoneButton} }
    sub cancelButton
        { $_[0]->{cancelButton} }
    sub mouseStep
        { $_[0]->{mouseStep} }
    sub startX
        { $_[0]->{startX} }
    sub startY
        { $_[0]->{startY} }
    sub stopX
        { $_[0]->{stopX} }
    sub stopY
        { $_[0]->{stopY} }

    sub entryCurrentZone
        { $_[0]->{entryCurrentZone} }
    sub frame
        { $_[0]->{frame} }
    sub simpleCanvasWidget
        { $_[0]->{simpleCanvasWidget} }
    sub simpleCanvasObj
        { $_[0]->{simpleCanvasObj} }
    sub simpleCanvasSize
        { $_[0]->{simpleCanvasSize} }
    sub editButton
        { $_[0]->{editButton} }
    sub deleteButton
        { $_[0]->{deleteButton} }
}

{ package Games::Axmud::EditWin::ZoneModel;

    use strict;
    use warnings;
#   use diagnostics;

    use Glib qw(TRUE FALSE);

    our @ISA = qw(
        Games::Axmud::Generic::EditWin Games::Axmud::Generic::ConfigWin
        Games::Axmud::Generic::FreeWin Games::Axmud::Generic::Win Games::Axmud
    );

    ##################
    # Constructors

#   sub new {}                  # Inherited from GA::Generic::ConfigWin

    ##################
    # Methods

    # Standard window object functions

#   sub winSetup {}             # Inherited from GA::Generic::ConfigWin

#   sub winEnable {}            # Inherited from GA::Generic::ConfigWin

#   sub winDesengage {}         # Inherited from GA::Generic::FreeWin

#   sub winDestroy {}           # Inherited from GA::Generic::FreeWin

#   sub winShowAll {}           # Inherited from GA::Generic::Win

#   sub drawWidgets {}          # Inherited from GA::Generic::ConfigWin

#   sub redrawWidgets {}        # Inherited from GA::Generic::Win

    # ->signal_connects

    # Other functions

    sub checkEditObj {

        # Called by $self->winEnable
        # Checks that the object stored in $self->editObj is the correct class of object
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments or if the check fails
        #   1 if the check succeeds

        my ($self, $check) = @_;

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->checkEditObj', @_);
        }

        if ($self->editObj && ! $self->editObj->isa('Games::Axmud::Obj::ZoneModel')) {
            return undef;
        } else {
            return 1;
        }
    }

    sub enableButtons {

        # Called by $self->drawWidgets
        # This 'edit' window needs a single button when a current zonemap is being viewed, but four
        #   buttons when a non-current zonemap is being edited
        #
        # Expected arguments
        #   $hBox       - The horizontal packing box in which the buttons live (not yet stored as
        #                   an IV)
        #
        # Return values
        #   An empty list on improper arguments
        #   Otherwise, a list containing the Gtk::Button object created

        my ($self, $hBox, $check) = @_;

        # Local variables
        my @emptyList;

        # Check for improper arguments
        if (! defined $hBox || defined $check) {

            $axmud::CLIENT->writeImproper($self->_objClass . '->enableButtons', @_);
            return @emptyList;
        }

        if ($axmud::CLIENT->checkCurrentZonemap($self->editObj->zonemapObj->name)) {
            return $self->enableSingleButton($hBox);
        } else {
            return $self->SUPER::enableButtons($hBox);
        }
    }

#   sub enableSingleButton {}   # Inherited from GA::Generic::ConfigWin

    sub setupNotebook {

        # Called by $self->enable
        # Creates the first tab for the notebook. The remaining tabs are created by
        #   $self->expandNotebook
        #
        # 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 . '->setupNotebook', @_);
        }

        # Tab setup (already created by the calling function)

        # Set up the rest of the first tab (all of it, in this case)
        $self->numberTab();

        # Set up the remaining tabs
        $self->expandNotebook();

        # Tab complete
        return 1;
    }

    sub expandNotebook {

        # Called by $self->setupNotebook
        # Set up additional tabs for the notebook
        #
        # 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 . '->expandNotebook', @_);
        }

        $self->reservedTab();

        return 1;
    }

#   sub saveChanges {}          # Inherited from GA::Generic::ConfigWin

    # Notebook tabs

    sub numberTab {

        # Number tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $currentFlag,
            @comboList, @comboList2, @comboList3,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->numberTab', @_);
        }

        # Tab setup
        my $grid = $self->addTab(
            $self->notebook,
            '_Number',
            ['Zone model settings'],
        );

        # Can't edit zone models in use
        if ($axmud::CLIENT->checkCurrentZonemap($self->editObj->zonemapObj->name)) {

            $currentFlag = TRUE;
        }

        # Left column
        if ($currentFlag) {

            $self->addLabel(
                $grid,
                '<b>Zone model settings</b> <i>(zone models belonging to zonemaps in use can\'t be'
                . ' edited)</i>',
                0, 12, 0, 1);
        } else {

            $self->addLabel($grid, '<b>Zone model settings</b>',
                0, 12, 0, 1);
        }

        $self->addLabel($grid, 'Zone model number',
            1, 3, 1, 2);
        $self->addEntry($grid, 'number', FALSE,
            3, 6, 1, 2, 4, 4);
        $self->addLabel($grid, 'Parent zonemap',
            1, 3, 2, 3);
        my $entry = $self->addEntry($grid, undef, FALSE,
            3, 6, 2, 3, 16, 16);
        $entry->set_text($self->editObj->zonemapObj->name);

        $self->addLabel($grid, 'Default winmap (\'main\' windows)',
            1, 3, 3, 4);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('winmapHash'));
        my $combo = $self->addComboBox($grid, 'defaultEnabledWinmap', \@comboList, '',
            FALSE,              # 'undef' value used
            3, 6, 3, 4);

        $self->addLabel($grid, '...when workspace grids disactivated',
            1, 3, 4, 5);
        my $combo2 = $self->addComboBox($grid, 'defaultDisabledWinmap', \@comboList, '',
            FALSE,              # 'undef' value used
            3, 6, 4, 5);

        $self->addLabel($grid, 'Default winmap (other windows)',
            1, 3, 5, 6);
        my $combo3 = $self->addComboBox($grid, 'defaultInternalWinmap', \@comboList, '',
            FALSE,              # 'undef' value used
            3, 6, 5, 6);

        $self->addLabel($grid, '<i>Coordinates in parent zonemap:</i>',
            1, 6, 6, 7);
        $self->addLabel($grid, 'Top-left X/Y',
            1, 3, 7, 8);
        $self->addEntry($grid, 'left', FALSE,
            3, 5, 7, 8, 4, 4);
        $self->addEntry($grid, 'top', FALSE,
            5, 6, 7, 8, 4, 4);
        $self->addLabel($grid, 'Bottom-right X/Y',
            1, 3, 8, 9);
        $self->addEntry($grid, 'right', FALSE,
            3, 5, 8, 9, 4, 4);
        $self->addEntry($grid, 'bottom', FALSE,
            5, 6, 8, 9, 4, 4);
        $self->addLabel($grid, 'Width (blocks)',
            1, 3, 9, 10);
        $self->addEntry($grid, 'width', FALSE,
            3, 6, 9, 10, 4, 4);
        $self->addLabel($grid, 'Height (blocks)',
            1, 3, 10, 11);
        $self->addEntry($grid, 'height', FALSE,
            3, 6, 10, 11, 4, 4);

        # Right column
        my $checkButton = $self->addCheckButton(
            $grid, 'Reserved windows (see next page)', 'reservedFlag', TRUE,
            7, 12, 1, 2);
        my $checkButton2 = $self->addCheckButton(
            $grid, 'Allow multiple layers', 'multipleLayerFlag', TRUE,
            7, 12, 2, 3);

        $self->addLabel($grid, 'Optional owner ID',
            7, 9, 3, 4);
        $self->addEntryWithIconButton($grid, 'ownerString', 'string', 0, undef,
            9, 12, 3, 4, 8, 8);

        $self->addLabel($grid, 'Start corner',
            7, 9, 4, 5);
        @comboList2 = ('top_left', 'top_right', 'bottom_left', 'bottom_right');
        my $combo4 = $self->addComboBox($grid, 'startCorner', \@comboList2, '',
            TRUE,               # No 'undef' value used
            9, 12, 4, 5);

        $self->addLabel($grid, 'Zone orientation',
            7, 9, 5, 6);
        @comboList3 = ('horizontal', 'vertical');
        my $combo5 = $self->addComboBox($grid, 'orientation', \@comboList3, '',
            TRUE,               # No 'undef' value used
            9, 12, 5, 6);

        $self->addLabel($grid, '<i>Maximum area settings (0 = no max):</i>',
            7, 12, 6, 7);
        $self->addLabel($grid, 'Max areas in zone',
            7, 9, 7, 8);
        my $entry2 = $self->addEntryWithIcon($grid, 'areaMax', 'int', 0, undef,
            9, 12, 7, 8, 4, 4);
        $self->addLabel($grid, 'Max areas in layer',
            7, 9, 8, 9);
        my $entry3 = $self->addEntryWithIcon($grid, 'visibleAreaMax', 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        $self->addLabel($grid, '<i>For unlimited areas, default size (0-60):</i>',
            7, 12, 9, 10);
        $self->addLabel($grid, 'Area width (blocks)',
            7, 9, 10, 11);
        my $entry4 = $self->addEntryWithIcon($grid, 'defaultAreaWidth', 'int', 0, 60,
            9, 12, 10, 11, 4, 4);
        $self->addLabel($grid, 'Area height (blocks)',
            7, 9, 11, 12);
        my $entry5 = $self->addEntryWithIcon($grid, 'defaultAreaHeight', 'int', 0, 60,
            9, 12, 11, 12, 4, 4);

        # Zone models can't be edited if the parent zonemap is a current zonemap in any session
        if ($currentFlag) {

            $checkButton->set_sensitive(FALSE);
            $checkButton2->set_sensitive(FALSE);
            $combo->set_sensitive(FALSE);
            $combo2->set_sensitive(FALSE);
            $combo3->set_sensitive(FALSE);
            $combo4->set_sensitive(FALSE);
            $combo5->set_sensitive(FALSE);
            $entry2->set_sensitive(FALSE);
            $entry3->set_sensitive(FALSE);
            $entry4->set_sensitive(FALSE);
            $entry5->set_sensitive(FALSE);
        }

        # Tab complete
        return 1;
    }

    sub reservedTab {

        # Reserved tab
        #
        # Expected arguments
        #   (none besides $self)
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $check) = @_;

        # Local variables
        my (
            $comboTitle,
            @columnList, @comboList, @comboList2,
        );

        # Check for improper arguments
        if (defined $check) {

            return $axmud::CLIENT->writeImproper($self->_objClass . '->reservedTab', @_);
        }

        # Tab setup
        # N.B. No sub-headings
        my $grid = $self->addTab($self->notebook, '_Reserved windows');

        # Set a combo title now so we don't accidentally use it
        $comboTitle = 'Select a task:';

        # Reserved windows
        $self->addLabel($grid, '<b>Reserved windows</b>',
            0, 12, 0, 1);
        $self->addLabel($grid, '<i>Types of window for which this zone is reserved</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Window/task name', 'text',
            'Window type', 'text',
        );

        my $slWidget = $self->addSimpleList($grid, undef, \@columnList,
            1, 12, 2, 8);

        # Initialise the list
        $self->refreshList_hashIV($slWidget, scalar (@columnList / 2), 'reservedHash');

        # Add entries/comboboxes for adding windows (zonemodels can't be edited if the parent
        #   zonemap is a current zonemap in any workspace grid)
        if (! $axmud::CLIENT->checkCurrentZonemap($self->editObj->zonemapObj->name)) {

            $self->addLabel($grid, 'Window type',
                1, 3, 8, 9);

            @comboList = ('main', 'map', 'protocol', 'fixed', 'custom', 'external');
            my $combo = $self->addComboBox($grid, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 8, 9);

            $self->addLabel($grid, 'Window name',
                1, 3, 9, 10);
            my $entry = $self->addEntryWithIcon($grid, undef, 'string', 1, undef,
                3, 6, 9, 10);
            $self->addLabel($grid, '(or) task name',
                6, 9, 9, 10);

            @comboList2 = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('taskPackageHash'));
            my $combo2 = $self->addComboBox($grid, undef, \@comboList2, $comboTitle,
                FALSE,              # 'undef' value allowed
                9, 12, 9, 10);

            # Add standard editing buttons to the simple list
            my $button = $self->addSimpleListButtons_hashIV(
                $grid,
                $slWidget,
                'reservedHash',
                10,
                $entry,
            );
            $button->signal_connect('clicked' => sub {

                my ($type, $text, $text2, $name);

                $type = $combo->get_active_text();
                if ($type eq 'main' || $type eq 'map') {

                    # Name is the same as the type
                    $name = $type;

                } else {

                    $text = $entry->get_text();
                    $text2 = $combo2->get_active_text();

                    if ($text2 && $text2 ne $comboTitle) {
                        $name = $text2;
                    } elsif ($self->checkEntryIcon($entry)) {
                        $name = $text;
                    }
                }

                if (defined $name) {

                    # Add a new key-value pair
                    $self->modifyEditHash_hashIV('reservedHash', $name, $type);

                    # Refresh the simple list and reset entry boxes
                    $self->refreshList_hashIV(
                        $slWidget,
                        scalar (@columnList / 2),
                        'reservedHash',
                    );

                    $self->resetEntryBoxes($entry);
                }
            });

            $combo->signal_connect('changed' => sub {

                # Make $entry and $combo2 sensitive or insensitive, depending on which window type
                #   is selected

                my $type = $combo->get_active_text();

                if ($type eq 'main' || $type eq 'map') {

                    $combo2->set_active(0);
                    $self->desensitiseWidgets($entry, $combo2);

                } elsif ($type ne 'custom') {

                    $self->sensitiseWidgets($entry);

                    $combo2->set_active(0);
                    $self->desensitiseWidgets($combo2);

                } else {

                    $self->sensitiseWidgets($entry);
                    $self->sensitiseWidgets($combo2);
                }
            });

            # The tab starts with both desensitised
            $self->desensitiseWidgets($entry, $combo2);
        }

        # Tab complete
        return 1;
    }

    ##################
    # Methods

    ##################
    # Accessors - set

    ##################
    # Accessors - get
}

# Package must return a true value
1
