# Copyright (C) 2011-2018 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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 table size
        my ($vBox, $table) = $self->addTab('_Line', $self->notebook);

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Line', $self->notebook);

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

        $self->addLabel($table, 'Buffer location',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($table, 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($table, 'Line number',
            1, 3, 3, 4);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 3, 4, 8, 8);
        $self->addLabel($table, 'Ends with newline character',
            1, 5, 4, 5);
        my $checkButton = $self->addCheckButton($table, 'newLineFlag', FALSE,
            5, 6, 4, 5, 1, 0.5);

        # Right column
        $self->addLabel($table, 'Time received',
            7, 9, 1, 2);
        $self->addEntry($table, 'time', FALSE,
            9, 12, 1, 2, 8, 8);
        $self->addLabel($table, 'No. of packets',
            7, 9, 2, 3);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            9, 12, 2, 3);
        $entry3->set_text($self->editObj->ivNumber('partList'));
        $self->addLabel($table, 'Empty line',
            7, 11, 4, 5);
        my $checkButton2 = $self->addCheckButton($table, 'emptyFlag', FALSE,
            11, 12, 4, 5, 1, 0.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($table, '<u>Text received</u>',
            1, 12, 5, 6);
        $self->addLabel($table, 'Original line (received from the world)',
            1, 12, 6, 7);
        my $entry4 = $self->addEntry($table, undef, TRUE,
            1, 12, 7, 8);
        $entry4->set_editable(FALSE);
        $entry4->set_text($self->editObj->line);

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

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

        my $button = $self->addButton(
            $table,
            '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 ($vBox, $table) = $self->addTab('_Tags', $self->notebook);

        # Axmud colour/style tags
        $self->addLabel($table, '<b>' . $axmud::SCRIPT . ' colour/style tags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 6, 2, 10);

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

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

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

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

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

        # Add a button
        my $button = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub tagsTab_refreshList {

        # Called by $self->tagsTab to refresh the GA::Gtk::Simple::List and Gtk2::TextView
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $columns    - The number of columns in the list
        #   $textView, $textView2, $textView3
        #               - The Gtk2::TextViews used
        #   $entry      - The Gtk2::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 ($vBox, $table) = $self->addTab('_Parts', $self->notebook);

        # Line parts
        $self->addLabel($table, '<b>Line parts</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table,
            '<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($table, 'partList', FALSE,
            1, 12, 4, 10,
            TRUE, TRUE, TRUE, FALSE,
            -1, 300);       # Fixed height
        my $button = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_MXP', $self->notebook);

        # MXP tag properties
        $self->addLabel($table, '<b>MXP tag properties</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);      # Fixed height

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

        # Add a button
        my $button = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub mxpTab_refreshList {

        # Called by $self->mxpTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 table size
        my ($vBox, $table) = $self->addTab('_Item', $self->notebook);

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Item', $self->notebook);

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

        $self->addLabel($table, 'Buffer location',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($table, 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($table, 'Item number',
            1, 3, 3, 4);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 3, 4);

        # Right column
        $self->addLabel($table, 'Time processed',
            7, 9, 1, 2);
        $self->addEntry($table, 'time', FALSE,
            9, 12, 1, 2);
        $self->addLabel($table, 'Instruction type',
            7, 9, 2, 3);
        $self->addEntry($table, '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($table, 'Instruction',
            1, 12, 4, 5);
        my $entry3 = $self->addEntry($table, '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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 table size
        my ($vBox, $table) = $self->addTab('_Item', $self->notebook);

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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->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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Item', $self->notebook);

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

        $self->addLabel($table, 'Buffer location',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($table, 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($table, 'Item number',
            1, 3, 3, 4);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 3, 4, 8, 8);

        # Right column
        $self->addLabel($table, 'Time sent',
            7, 9, 1, 2);
        $self->addEntry($table, '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($table, 'Command',
            1, 12, 4, 5);
        my $entry3 = $self->addEntry($table, '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 ($vBox, $table) = $self->addTab('_Moves', $self->notebook);

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

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

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

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

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 {}        # Inherited from GA::Generic::ConfigWin

#   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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my $obj;

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Cage settings</b>',
            0, 12, 0, 1);

        $self->addLabel($table, '<i>Cage</i>',
            1, 12, 1, 2);
        $self->addLabel($table, 'Cage name',
            2, 5, 2, 3);
        $self->addEntry($table, 'name', FALSE,
            5, 12, 2, 3, 42, 42);               # Cage's max names are 42 characters

        $self->addLabel($table, 'Cage type',
            2, 5, 3, 4);
        $self->addEntry($table, 'cageType', FALSE,
            5, 7, 3, 4, 8, 8);

        $self->addLabel($table, 'Inferior cage',
            2, 5, 4, 5);
        my $entry = $self->addEntry($table, 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($table, '<i>Associated profile</i>',
            1, 12, 5, 6);

        $self->addLabel($table, 'Profile name',
            2, 5, 6, 7);
        $self->addEntry($table, 'profName', FALSE,
            5, 7, 6, 7, 16, 16);

        # Right column
        $self->addLabel($table, 'Standard cage?',
            8, 10, 3, 4);
        $self->addCheckButton($table, 'standardFlag', FALSE,
            10, 12, 3, 4);

        $self->addLabel($table, 'Profile category',
            8, 10, 6, 7);
        $self->addEntry($table, 'profCategory', FALSE,
            10, 12, 6, 7);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Commands', $self->notebook);

        # Interpolated command strings
        $self->addLabel($table, '<b>Interpolated command strings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Words', $self->notebook);

        # Interpolated word strings
        $self->addLabel($table, '<b>Interpolated word strings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub addCageButtons {

        # Called by $self->commandsTab and $self->wordsTab to create the editing buttons
        #   beneath the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $table      - The current Gtk2::Table displayed in the notebook
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $entry      - A Gtk2::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, $table, $slWidget, $entry, $iv, $columns, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $table || ! 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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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::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 table size
        my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # 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($table);

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Interace list
        $self->addLabel($table, '<b>' . ucfirst($self->singular) . ' list</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 170);      # Fixed height

        # 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($table, $slWidget, (scalar @columnList / 2));

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub interfaces1Tab_refreshList {

        # Called by $self->interfaces1Tab (etc) to reset the GA::Gtk::Simple::List
        #
        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Interface list
        $self->addLabel($table, '<b>' . ucfirst($self->singular) . ' list</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 170);      # Fixed height

        # 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($table, $slWidget, (scalar @columnList / 2));

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub interfaces2Tab_refreshList {

        # Called by $self->interfaces2Tab (etc) to reset the GA::Gtk::Simple::List
        #
        # 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::Gtk::Simple::List
        #
        # Expected arguments
        #   $table      - The current Gtk2::Table displayed in the notebook
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

        # Add entry boxes and edit buttons
        $self->addLabel(
            $table, ucfirst($self->singular) . ' stimulus <i>(' . $self->stimulus . ')</i>',
            1, 3, 8, 9);
        # For hooks, use a combo; for everything else, use an entry. For aliases, leave room for an
        #   extra button beside the entry
        my ($entry, $combo);
        if ($self->editObj->cageType eq 'hook') {

            @comboList = sort {$a cmp $b} ($self->interfaceModelObj->ivKeys('hookEventHash'));
            $combo = $self->addComboBox($table, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 8, 9);

        } elsif ($self->editObj->cageType eq 'alias') {

            $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
                3, 10, 8, 9);

        } else {

            $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
                3, 12, 8, 9);
        }

        my $label = $self->addLabel(
            $table, ucfirst($self->singular) . ' response <i>(' . $self->response . ')</i>',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        $self->addLabel($table, 'Name <i>(optional)</i>',
            1, 3, 10, 11);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            3, 6, 10, 11);

        my ($checkButton, $checkButton2, $checkButton3);
        if ($self->singular eq 'trigger') {

            $self->addLabel($table, 'Starts enabled',
                6, 7, 10, 11);
            $checkButton = $self->addCheckButton($table, undef, TRUE,
                7, 8, 10, 11, 1, 0.5);
            $self->addLabel($table, 'Splitter',
                8, 9, 10, 11);
            $checkButton2 = $self->addCheckButton($table, undef, TRUE,
                9, 10, 10, 11, 1, 0.5);
            $self->addLabel($table, 'Rewriter',
                10, 11, 10, 11);
            $checkButton3 = $self->addCheckButton($table, undef, TRUE,
                11, 12, 10, 11, 1, 0.5);
            $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 {

            $self->addLabel($table, 'Starts enabled',
                7, 11, 10, 11);
            $checkButton = $self->addCheckButton($table, undef, TRUE,
                11, 12, 10, 11, 1, 0.5);
        }
        # New interfaces should be enabled by default
        $checkButton->set_active(TRUE);

        my $button = $self->addButton(
            $table,
            '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, $rewriterFlag, $stimulusSwitch,
                $responseSwitch, $enabledSwitch, $rewriterSwitch, $nameSwitch, $rewriterString,
                $splitterFlag, $splitterSwitch, $splitterString, $result,
            );

            # Get the interface stimulus
            if ($self->editObj->cageType eq 'hook' && $self->checkEntryIcon($entry2, $entry3)) {
                $stimulus = $combo->get_active_text();      # Compulsory
            } elsif ($self->checkEntryIcon($entry, $entry2, $entry3)) {
                $stimulus = $entry->get_text();             # Compulsory
            }

            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 ($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
                        . ' -' . $nameSwitch . ' <' . $interfaceName
                        . '> -d ' . $self->editObj->profName,
                        $self->pseudoCmdMode,
                    );

                } else {

                    $result = $self->session->pseudoCmd(
                        'add' . $self->singular
                        . ' -' . $stimulusSwitch . ' <' . $stimulus
                        . '> -' . $responseSwitch . ' <' . $response
                        . '> -' . $enabledSwitch . ' ' . $enabledFlag
                        . $splitterString . $rewriterString
                        . ' -d ' . $self->editObj->profName,
                        $self->pseudoCmdMode,
                    );
                }

                if ($result) {

                    # Refresh (both) lists
                    $self->interfaces1Tab_refreshList();
                    $self->interfaces2Tab_refreshList();
                }
            }
        });

        my $button2 = $self->addButton(
            $table,
            '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(
            $table,
            'Delete',
            'Delete the selected ' . $self->singular,
            undef,
            3, 5, 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(
            $table,
            'Dump',
            'Display this list of ' . $self->plural . ' in the \'main\' window',
            undef,
            8, 10, 11, 12,
        );
        $button4->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 $button5 = $self->addButton(
            $table,
            'Refresh list',
            'Refresh the list of ' . $self->plural,
            undef,
            10, 12, 11, 12,
        );
        $button5->signal_connect('clicked' => sub {

            # Refresh (both) lists
            $self->interfaces1Tab_refreshList();
            $self->interfaces2Tab_refreshList();
        });

        if ($self->editObj->cageType eq 'alias') {

            my $button6 = $self->addButton(
                $table,
                'Recommended: add ^...$',
                'Adds symbols to match the pattern against the whole world command',
                undef,
                10, 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;
    }

    ##################
    # 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::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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Triggers', $self->notebook);

        # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Aliases', $self->notebook);

        # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Macros', $self->notebook);

        # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('T_imers', $self->notebook);

        # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Hooks', $self->notebook);

        # 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 table size
        my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # 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($table);

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Routes', $self->notebook);

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

        return 1;
    }

    sub routes1Tab {

        # Routes1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Routes list
        $self->addLabel($table, '<b>Routes list</b>',
            0, 13, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 13, 2, 8,
            -1, 170);      # Fixed height

        # 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($table, $slWidget, (scalar @columnList / 2));

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub routes1Tab_refreshList {

        # Called by $self->routes1Tab to refresh the GA::Gtk::Simple::List
        #
        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Routes list
        $self->addLabel($table, '<b>Routes list</b>',
            0, 13, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 13, 2, 8,
            -1, 170);      # Fixed height

        # 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($table, $slWidget, (scalar @columnList / 2));

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub routes2Tab_refreshList {

        # Called by $self->routes2Tab to refresh the GA::Gtk::Simple::List
        #
        # 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::Gtk::Simple::List
        #
        # Expected arguments
        #   $table      - The current Gtk2::Table displayed in the notebook
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $columns    - The number of columns
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @typeList;

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

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

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

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

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

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

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

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

        # Add editing buttons
        my $button = $self->addButton(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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 {}        # Inherited from GA::Generic::ConfigWin

#   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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

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

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

        $self->addLabel($table, 'Protocol',
            1, 3, 2, 3);
        @comboList = ('MudMaster', 'zChat');
        my $comboBox = $self->addComboBox($table, 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($table, 'IP address',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'ip', 'string', 1, undef,
            3, 6, 3, 4);
        $self->addLabel($table, 'Port',
            1, 3, 4, 5);
        $self->addEntryWithIcon($table, 'port', 'int', 0, 65535,
            3, 6, 4, 5);
        $self->addLabel($table, 'Email',
            1, 3, 5, 6);
        $self->addEntry($table, 'email', FALSE,
            3, 12, 5, 6);

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

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 {}        # 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->overrideTab();

        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 object 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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

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

        # Colour settings
        $self->addLabel($table, '<i><u>Colour settings</u></i>',
            1, 12, 2, 3);

        my $hiddenButton = $self->nameTab_addRow($table, 'textColour', 'Text colour', 3);
        my $hiddenButton2 = $self->nameTab_addRow($table, 'underlayColour', 'Underlay colour', 5);
        my $hiddenButton3 = $self->nameTab_addRow(
            $table,
            'backgroundColour',
            'Background colour',
            7,
        );

        my $button = $self->addButton($table,
            'Set underlay to match background',
            'Update the underlay colour',
            undef,
            1, 6, 9, 10);
        $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 Gnome2::Canvas/Gtk2::Entry objects
            $hiddenButton2->clicked();
        });

        my $button2 = $self->addButton($table,
            'Swap text and background colours',
            'Update the text and background colours',
            undef,
            6, 12, 9, 10);
        $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 Gnome2::Canvas/Gtk2::Entry objects
            $hiddenButton->clicked();
            $hiddenButton2->clicked();
            $hiddenButton3->clicked();
        });

        # Font settings
        $self->addLabel($table, '<i><u>Font settings</u></i>',
            1, 12, 10, 11);

        $self->addLabel($table, 'Font',
            1, 4, 11, 12);

        my $entry = $self->addEntry($table, undef, FALSE,
            4, 6, 11, 12);
        $entry->set_text($self->editObj->font . ' ' . $self->editObj->fontSize);

        my $button3 = $self->addButton($table, 'Change', 'Change this font', undef,
            6, 7, 11, 12);
        $button3->signal_connect('clicked' => sub {

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

            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($table, undef, FALSE,
            9, 11, 11, 12);
        $entry2->set_text($axmud::CLIENT->constFont . ' ' . $axmud::CLIENT->constFontSize);

        my $button4 = $self->addButton($table, 'Use default', 'Use the default font', undef,
            11, 12, 11, 12);
        $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);
        });

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nameTab_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
        #   $table      - The Gtk2::Table 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 Gtk2::Table displayed in this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns a Gtk2::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, $table, $iv, $text, $row, $check) = @_;

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

        # Check for improper arguments
        if (
            ! defined $table || ! defined $iv || ! defined $text || ! defined $row
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->nameTab_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($table, $text,
            1, 3, $row, ($row + 1));

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

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

        my $button = $self->addButton($table, '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($table, undef, \@tagList, $title,
            TRUE,           # no 'undef'
            4, 6, ($row + 1), ($row + 2));

        my $button2 = $self->addButton($table, '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($table, $rgbDefault, undef,
            8, 9, $row, ($row + 1));

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

        my $button3 = $self->addButton($table, '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 Gtk2::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 = Gtk2::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
        my ($vBox, $table) = $self->addTab('_Colour overrides', $self->notebook);

        # Colour overrides
        $self->addLabel($table, '<b>Colour overrides</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, 'overrideAllFlag', TRUE,
            1, 12, 2, 3);
        $checkButton->set_label(
#            'Ignore all colour tags except the textview\'s default text, underlay and background'
#            . ' colours',
            'Ignore all colour tags except the normal text and background colours',
        );

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

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 3, 9,
            -1, 220);       # Fixed height

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

        # Add editing widgets
        $self->addLabel($table,
            '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($table, undef, \&overrideTab_checkEntry, undef, undef,
            9, 12, 9, 10,
            16, 16);
        $self->addLabel($table,
            'Replacement tag - Standard, Xterm or RGB, or leave empty to use default colours',
            1, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, 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(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub overrideTab_refreshList {

        # Called by $self->overrideTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 Gtk2::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;
            }
        }
    }

    ##################
    # 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 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 object 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
        #   $table  -   The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $allTextMsg, $current, $matchFlag,
            @comboList,
        );

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Component name
        $self->addLabel($table, '<b>Component name</b>',
            0, 6, 0, 1);
        $self->addEntry($table, 'name', FALSE,
            1, 6, 1, 2, 16, 16);

        # Component type
        $self->addLabel($table, '<b>Component type</b>',
            7, 13, 0, 1);
        $self->addEntry($table, 'type', FALSE,
            8, 13, 1, 2, 16, 16);

        # Component expected size
        $self->addLabel($table, '<b>Component expected size</b>',
            0, 13, 2, 3);

        $self->addLabel(
            $table, 'Fixed size (range 0-256) (0 = use other properties to find the component)',
            1, 9, 3, 4);
        my $entry = $self->addEntryWithIcon($table, 'size', 'int', 0, 256,
            9, 13, 3, 4);

        $self->addLabel($table, 'Minimum size (range 0-256) (0 = this component is optional)',
            1, 9, 4, 5);
        my $entry2 = $self->addEntryWithIcon($table, 'minSize', 'int', 0, 256,
            9, 13, 4, 5);

        $self->addLabel($table, 'Maximum size (range 1-256)',
            1, 9, 5, 6);
        my $entry3 = $self->addEntryWithIcon($table, '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($table, '<b>Component analyse mode</b>',
            0, 13, 6, 7);

        if ($self->editObj->type eq 'verb_special') {

            $self->addLabel(
                $table,
                '<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(
                $table, undef, '\'check_line\'', 'analyseMode',
                'check_line',   # IV set to this value when toggled
                TRUE,           # Sensitive widget
                1, 4, 8, 9);
            $self->addLabel(
                $table,
                'Check each line, one at a time, against all patterns/tags <i>(recommended)</i>',
                4, 12, 8, 9);

            ($group, $radioButton) = $self->addRadioButton(
                $table, $group, '\'check_pattern_tag\'', 'analyseMode', 'check_pattern_tag', TRUE,
                1, 4, 9, 10);
            $self->addLabel($table, 'Check each pattern/tag, one at a time, against all lines',
                4, 12, 9, 10);

            $self->addLabel(
                $table,
                '<i>NB Most patterns and tags are ignored if the component has a fixed size</i>',
                1, 13, 10, 11);
            $self->addLabel(
                $table,
                '<i>NB The two sets of start patterns/tags are always checked together</i>',
                1, 13, 11, 12);

        }

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Locator options', $self->notebook);

        # Locator task options
        $self->addLabel($table, '<b>Locator task options</b>',
            0, 13, 0, 1);
        $self->addLabel(
            $table,
            '<i>Options which affect the way the Locator task interprets the text of a'
            . ' component</i>',
            1, 13, 1, 2);

        $self->addLabel($table, 'Locator doesn\'t use normal/bold colour tags interchangeably',
            1, 12, 2, 3);
        $self->addCheckButton($table, 'boldSensitiveFlag', TRUE,
            12, 13, 2, 3, 1, 0.5);

        $self->addLabel(
            $table,
            'Locator only considers colours/styles that applied at the beginning of the line',
            1, 12, 3, 4);
        my $checkButton = $self->addCheckButton($table, 'useInitialTagsFlag', TRUE,
            12, 13, 3, 4, 1, 0.5);

        $self->addLabel(
            $table,
            'Locator only considers colours/styles that were explicity specified on this line',
            1, 12, 4, 5);
        my $checkButton2 = $self->addCheckButton($table, 'useExplicitTagsFlag', TRUE,
            12, 13, 4, 5, 1, 0.5);

        $self->addLabel($table, 'Locator treats this component as if it were a single line',
            1, 12, 5, 6);
        $self->addCheckButton($table, 'combineLinesFlag', TRUE,
            12, 13, 5, 6, 1, 0.5);

        $self->addLabel($table, '<b>Locator task ignore options</b>',
            0, 13, 6, 7);
        $self->addLabel(
            $table,
            '<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($table, '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($table, 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(
            $table,
            'Locator ignores the first <i>n</i> characters (0 - use whole line)',
            1, 9, 9, 10);
        $self->addEntryWithIcon($table, 'ignoreFirstChars', 'int', 0, undef,
            9, 13, 9, 10);
        $self->addLabel(
            $table,
            'Locator uses only the first <i>n</i> characters (0 - use whole line)',
            1, 9, 10, 11);
        $self->addEntryWithIcon($table, 'useFirstChars', 'int', 0, undef,
            9, 13, 10, 11);
        $self->addLabel(
            $table,
            'Locator uses only the contents of any backreference(s), if this pattern matches',
            1, 9, 11, 12);
        $self->addEntryWithIcon($table, 'usePatternBackRefs', 'string', 0, undef,
            1, 13, 12, 13);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Other options', $self->notebook);

        # Non-extractable components
        $self->addLabel($table, '<b>Non-extractable components</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>List of component types/names which should NOT be extracted, if this component can'
            . ' be extracted</i>',
            1, 12, 1, 2);

        $self->addTextView($table, 'noExtractList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, TRUE, FALSE,    # Treat as list, remove empty lines, do remove whitespace
            -1, 150);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook)
            = $self->addInnerNotebookTab('_Patterns / tags', $self->notebook);

        # Add tabs to the inner notebook

        # Start patterns / tags
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _1',
            'startPatternList', 'startTagList', 'startAllFlag', 'startTagMode',
            '<b>Start patterns / tags</b>',
            '<i>The start of the component DOES match one of these patterns</i>',
            '<i>...DOES contain one of these tags</i>',
            '<i>The start of the component DOES match all of these patterns</i>',
            '<i>...DOES contain all of these tags</i>',
        );

        # Start patterns / tags (2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _2',
            'startNoPatternList', 'startNoTagList', 'startNoAllFlag', 'startNoTagMode',
            '<b>Start patterns / tags (2)</b>',
            '<i>The start of the component does NOT match one of these patterns</i>',
            '<i>...does NOT contain one of these tags</i>',
            '<i>The start of the component does NOT match all of these patterns</i>',
            '<i>...does NOT contain all of these tags</i>',
        );

        # Skip patterns / tags
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _3',
            'skipPatternList', 'skipTagList', 'skipAllFlag', 'skipTagMode',
            '<b>Skip patterns / tags</b>',
            '<i>Patterns which mean the line should be ignored</i>',
            '<i>Tags found on an ignorable line</i>',
            '<i>Patterns which mean the line should be ignored</i>',
            '<i>Tags found on an ignorable line</i>',
        );

        # Stop before patterns / tags
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _4',
            'stopBeforePatternList', 'stopBeforeTagList', 'stopBeforeAllFlag',
                'stopBeforeTagMode',
            '<b>Stop before patterns / tags</b>',
            '<i>Stop before the first line which DOES match one of these patterns</i>',
            '<i>...which DOES contain one of these tags</i>',
            '<i>Stop before the first line which DOES match all of these patterns</i>',
            '<i>...which DOES contain all of these tags</i>',
        );

        # Stop before patterns / tags (2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _5',
            'stopBeforeNoPatternList', 'stopBeforeNoTagList', 'stopBeforeNoAllFlag',
                'stopBeforeNoTagMode',
            '<b>Stop before patterns / tags (2)</b>',
            '<i>Stop before the first line which does NOT match one of these patterns</i>',
            '<i>...which does NOT contain one of these tags</i>',
            '<i>Stop before the first line which does NOT match all of these patterns</i>',
            '<i>...which does NOT contain all of these tags</i>',
        );

        # Stop at patterns / tags
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _6',
            'stopAtPatternList', 'stopAtTagList', 'stopAtAllFlag', 'stopAtTagMode',
            '<b>Stop at patterns / tags</b>',
            '<i>Stop at the first line which DOES match one of these patterns</i>',
            '<i>...which DOES contain one of these tags</i>',
            '<i>Stop at the first line which DOES match all of these patterns</i>',
            '<i>...which DOES contain all of these tags</i>',
        );

        # Stop at patterns / tags (2)
        $self->patternsTags1Tab(
            $innerNotebook,
            'Page _7',
            'stopAtNoPatternList', 'stopAtNoTagList', 'stopAtNoAllFlag', 'stopAtNoTagMode',
            '<b>Stop at patterns / tags (2)</b>',
            '<i>Stop at the first line which does NOT match one of these patterns</i>',
            '<i>... which does NOT contain one of these tags</i>',
            '<i>Stop at the first line which does NOT match all of these patterns</i>',
            '<i>...which does NOT contain all of these tags</i>',
        );

        # 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 Gtk2::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 Gtk2::Label at the
        #           top of the tab; two out of the remaining four are used in the other Gtk2::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 ($vBox, $table) = $self->addTab($tabName, $innerNotebook);

        # Title label, e.g. Start patterns / tags
        $self->addLabel($table, $stringList[0],
            0, 4, 0, 1);

        # First group of radio buttons (at top; ->signal_connects appear below)
        my ($group, $radioButton, $radioButton2);
        ($group, $radioButton) = $self->addRadioButton(
            $table, undef, 'Line contains at least one', undef, undef, TRUE,
            6, 10, 0, 1);
        ($group, $radioButton2) = $self->addRadioButton(
            $table, $group, 'Line contains all', undef, undef, TRUE,
            10, 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(
            $table, undef, 'Any tags', undef, undef, TRUE,
            4, 6, 6, 7);
        ($group2, $radioButton12) = $self->addRadioButton(
            $table, $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(
            $table, $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(
            $table, $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($table, '',
            1, 12, 1, 2);
        my $textView = $self->addTextView($table, $patternListIV, TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 110);                  # Fixed height

        # Second label and textview, e.g. 'Tags found at the start of the component'
        my $label2 = $self->addLabel($table, '',
            1, 4, 6, 7);
        my $textView2 = $self->addTextView($table, $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($stringList[1]);
            $label2->set_markup($stringList[2]);

        } else {

            $label->set_markup($stringList[3]);
            $label2->set_markup($stringList[4]);
        }

        # ->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($stringList[1]);
                $label2->set_markup($stringList[2]);
            }
        });

        $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($stringList[3]);
                $label2->set_markup($stringList[4]);
            }
        });

        # 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($table, undef, \@comboList, 'Standard text colour tags:',
            TRUE,               # No 'undef' value used
            4, 10, 7, 8);
        my $button = $self->patternsTags1Tab_addButton(
            $table,
            7,
            $tagListIV,
            $buffer,
            $comboBox,
            'Standard text colour tags:',
        );
        my $button2 = $self->patternsTags1Tab_addBoldButton(
            $table,
            7,
            $tagListIV,
            $buffer,
            $comboBox,
            'Standard text colour tags:',
        );

        # Second set - standard underlay colour tags
        my $comboBox2 = $self->addComboBox(
            $table,
            undef,
            \@comboList2,
            'Standard underlay colour tags:',
            TRUE,               # No 'undef' value used
            4, 10, 8, 9);
        my $button3 = $self->patternsTags1Tab_addButton(
            $table,
            8,
            $tagListIV,
            $buffer,
            $comboBox2,
            'Standard underlay colour tags:',
        );
        my $button4 = $self->patternsTags1Tab_addBoldButton(
            $table,
            8,
            $tagListIV,
            $buffer,
            $comboBox2,
            'Standard underlay colour tags:',
        );

        # Third set - xterm tags
        $self->addLabel($table, 'xterm tags:',
            4, 6, 9, 10);
        my $entry = $self->addEntryWithIcon(
            $table,
            undef,
            \&patternsTags1Tab_checkEntry,
            undef,
            undef,
            6, 10, 9, 10);

        # (Only one button of this type, for this tab, so it doesn't have its own function)
        my $button5 = $self->addButton(
            $table,
            '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($table, 'RGB tags:',
            4, 6, 10, 11);
        my $entry2 = $self->addEntryWithIcon(
            $table,
            undef,
            \&patternsTags1Tab_checkEntry2,
            undef,
            undef,
            6, 10, 10, 11);

        # (Only one button of this type, for this tab, so it doesn't have its own function)
        my $button6 = $self->addButton(
            $table,
            '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($table, undef, \@comboList3, 'Style tags:',
            TRUE,               # No 'undef' value used
            4, 10, 11, 12);
        my $button7 = $self->patternsTags1Tab_addButton(
            $table,
            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(
                    0,
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$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(
                    1,
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$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(
                    2,
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$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(
                    3,
                    $textView2,
                    [$comboBox, $button, $button2],
                    [$comboBox2, $button3, $button4],
                    [$entry, $button5],
                    [$comboBox3, $button6],
                );
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub patternsTags1Tab_checkEntry {

        # Called by $self->patternsTags1Tab to check the text in the first Gtk2::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk2::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 Gtk2::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk2::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 Gtk2::Button next to a combobox
        #
        # Expected arguments
        #   $table          - The Gtk2::Table
        #   $row            - The position in the table of the buttons
        #   $iv             - The IV being edited (e.g. 'startTagList')
        #   $buffer         - The buffer of the Gtk2::TextView used to display the IV
        #   $comboBox       - The Gtk2::ComboBox used alongside the Gtk2::Button
        #   $title          - The title used at the top of the combobox
        #
        # Return values
        #   'undef' on improper arguments
        #   The Gtk2::Button created, otherwise

        my ($self, $table, $row, $iv, $buffer, $comboBox, $title, $check) = @_;

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

        my $button = $self->addButton(
            $table,
            '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 Gtk2::Button next to a combobox
        #
        # Expected arguments
        #   $table          - The Gtk2::Table
        #   $row            - The position in the table of the buttons
        #   $iv             - The IV being edited (e.g. 'startTagList')
        #   $buffer         - The buffer of the Gtk2::TextView used to display the IV
        #   $comboBox       - The Gtk2::ComboBox used alongside the Gtk2::Button
        #   $title          - The title used at the top of the combobox
        #
        # Return values
        #   'undef' on improper arguments
        #   The Gtk2::Button created, otherwise

        my ($self, $table, $row, $iv, $buffer, $comboBox, $title, $check) = @_;

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

        my $button = $self->addButton(
            $table,
            '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 Gtk2::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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Stop at patterns (2)
        $self->addLabel($table, '<b>Stop at patterns (2)</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Stop at the first line which DOES match one of these patterns</i>',
            1, 12, 1, 2);

        $self->addTextView($table, 'stopAtPatternList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                  # Fixed height

        # Stop at tags (2)
        $self->addLabel($table, '<b>Stop at tags (2)</b>',
            0, 12, 6, 7);
        $self->addLabel($table,
            '<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($table, 'stopAtTagList', TRUE,
            1, 6, 8, 12,
            undef, undef, undef, undef,
            -1, 130);       # Fixed height
        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($table, undef, \@comboList, 'Colour tags',
            TRUE,               # No 'undef' value used
            7, 11, 8, 9);
        my $button = $self->patternsTags1Tab_addButton(
            $table,
            11, 8,
            'stopAtTagList',
            $buffer,
            $comboBox,
            'Colour tags:',
        );

        my $comboBox2 = $self->addComboBox($table, undef, \@comboList2, 'Underlay tags',
            TRUE,               # No 'undef' value used
            7, 11, 9, 10);
        my $button2 = $self->patternsTags1Tab_addButton(
            $table,
            11, 9,
            'stopAtTagList',
            $buffer,
            $comboBox2,
            'Underlay tags:',
        );

        my $comboBox3 = $self->addComboBox($table, undef, \@comboList3, 'Style tags',
            TRUE,               # No 'undef' value used
            7, 11, 10, 11);
        my $button3 = $self->patternsTags1Tab_addButton(
            $table,
            11, 10,
            'stopAtTagList',
            $buffer,
            $comboBox3,
            'Style tags:',
        );

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub patternsTags8Tab {

        # PatternsTags8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _8', $innerNotebook);

        # Stop at capitalised lines
        $self->addLabel($table, '<b>Stop at capitalised lines</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            'Stop at the n-th line which starts with a capital letter (0 - ignore)',
            1, 8, 1, 2);
        $self->addEntryWithIcon($table, 'upperCount', 'int', 0, 16,
            8, 12, 1, 2);

        $self->addLabel($table,
            'Stop at the n-th line which starts with a lower-case letter or number (0 - ignore)',
            1, 8, 2, 3);
        $self->addEntryWithIcon($table, 'otherCount', 'int', 0, 16,
            8, 12, 2, 3);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub patternsTags9Tab {

        # PatternsTags9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _9', $innerNotebook);

        # Stop before mode
        $self->addLabel($table, '<b>Stop before mode</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'default\'', 'stopBeforeMode',
            'default',      # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel($table, 'Don\'t use \'Stop before mode\'',
            4, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'no_char\'', 'stopBeforeMode', 'no_char', TRUE,
            1, 4, 2, 3);
        $self->addLabel(
            $table, 'Stop one line before the first line containing no characters at all',
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'no_letter_num\'', 'stopBeforeMode', 'no_letter_num', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $table, 'Stop one line before the first line containing no alphanumeric characters',
            4, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table,
            $group,
            '\'no_start_letter_num\'',
            'stopBeforeMode',
            'no_start_letter_num',
            TRUE,
            1, 4, 4, 5);
        $self->addLabel(
            $table,
            'Stop one line before the first line which doesn\'t start with an alphanumeric'
            . ' character',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'no_tag\'', 'stopBeforeMode', 'no_tag', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $table,
            'Stop one line before the first line containing no colour/style tags at all',
            4, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'has_letter_num\'', 'stopBeforeMode', 'has_letter_num', TRUE,
            1, 4, 6, 7);
        $self->addLabel(
            $table,
            'Stop one line before the first line which DOES contain alphanumeric characters',
            4, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $table,
            $group,
            '\'has_start_letter_num\'',
            'stopBeforeMode',
            'has_start_letter_num',
            TRUE,
            1, 4, 7, 8);
        $self->addLabel(
            $table,
            'Stop one line before the first line which DOES start with an alphanumeric character',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'has_tag\'', 'stopBeforeMode', 'has_tag', TRUE,
            1, 4, 8, 9);
        $self->addLabel(
            $table,
            'Stop one line before the first line which DOES contain colour/style tags',
            4, 12, 8, 9);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub patternsTags10Tab {

        # PatternsTags10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 1_0', $innerNotebook);

        # Stop at mode
        $self->addLabel($table, '<b>Stop at mode</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'default\'', 'stopAtMode',
            'default',      # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel($table, 'Don\'t use \'Stop at mode\'',
            4, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'no_char\'', 'stopAtMode', 'no_char', TRUE,
            1, 4, 2, 3);
        $self->addLabel(
            $table, 'Stop at the first line containing no characters at all',
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'no_letter_num\'', 'stopAtMode', 'no_letter_num', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $table, 'Stop at the first line containing no alphanumeric characters',
            4, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'no_start_letter_num\'', 'stopAtMode', 'no_start_letter_num', TRUE,
            1, 4, 4, 5);
        $self->addLabel(
            $table, 'Stop at the first line which doesn\'t start with an alphanumeric character',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'no_tag\'', 'stopAtMode', 'no_tag', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $table,
            'Stop at the first line containing no colour/style tags at all',
            4, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'has_letter_num\'', 'stopAtMode', 'has_letter_num', TRUE,
            1, 4, 6, 7);
        $self->addLabel(
            $table, 'Stop at the first line which DOES contain alphanumeric characters',
            4, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'has_start_letter_num\'', 'stopAtMode', 'has_start_letter_num', TRUE,
            1, 4, 7, 8);
        $self->addLabel(
            $table, 'Stop at the first line which DOES start with an alphanumeric character',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'has_tag\'', 'stopAtMode', 'has_tag', TRUE,
            1, 4, 8, 9);
        $self->addLabel(
            $table,
            'Stop at the first line which DOES contain colour/style tags',
            4, 12, 8, 9);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Dictionary name
        $self->addLabel($table, '<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($table, '',
            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($table, 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($table, '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($table);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 object 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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            @pbList, @comboList,
            %comboHash,
        );

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Current dictionary
        $self->addLabel($table, '<b>Current dictionary</b>',
            7, 12, 0, 1);

        my $checkbutton = Gtk2::CheckButton->new();
        if ($self->session->currentDict eq $self->editObj) {

            $checkbutton->set_active(TRUE);
        }
        $checkbutton->set_sensitive(FALSE);
        $table->attach_defaults($checkbutton, 8, 9, 1, 2);

        # Language
        $self->addLabel($table, '<b>Language</b>',
            0, 6, 3, 4);
        $self->addEntryWithIcon($table, '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($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            1, 4, 5, 6);
        my $button = $self->addButton(
            $table,
            '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) . "?\n\n(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($table, '<b>Word order</b>',
            7, 12, 3, 4);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'noun_adj\'', 'nounPosn',
            'noun_adj',     # IV set to this value when toggled
            TRUE,           # Sensitive widget
            8, 10, 4, 5);
        $self->addLabel($table, 'Noun-adjective (e.g. French, Spanish)',
            10, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'adj_noun\'', 'nounPosn', 'adj_noun', TRUE,
            8, 10, 5, 6);
        $self->addLabel($table, 'Adjective-noun (e.g. English, German)',
            10, 12, 5, 6);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Articles', $self->notebook);

        # Definite articles
        $self->addLabel($table, '<b>Definite articles</b>',
            0, 6, 0, 2);
        $self->addLabel($table, '<i>Words for \'the\' in the dictionary\'s language</i>',
            1, 6, 2, 4);
        $self->addTextView($table, 'definiteList', TRUE,
            1, 6, 4, 6);

        # Indefinite articles
        $self->addLabel($table, '<b>Indefinite articles</b>',
            0, 6, 6, 8);
        $self->addLabel($table, '<i>Words for \'a\' / \'an\' in the dictionary\'s language</i>',
            1, 6, 8, 10);
        $self->addTextView($table, 'indefiniteList', TRUE,
            1, 6, 10, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Conjunctions', $self->notebook);

        # Conjunctions
        $self->addLabel($table, '<b>Conjunctions</b>',
            0, 6, 0, 2);
        $self->addLabel($table, '<i>Words for \'and\' in the dictionary\'s language</i>',
            1, 6, 2, 4);
        $self->addTextView($table, 'andList', TRUE,
            1, 6, 4, 6);

        $self->addLabel($table, '<i>Words for \'or\' in the dictionary\'s language</i>',
            1, 6, 8, 10);
        $self->addTextView($table, 'orList', TRUE,
            1, 6, 10, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('N_umbers', $self->notebook);

        # Number terms
        $self->addLabel($table, '<b>Number terms</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 260);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Term',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Number (indeterminate = -1):',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add buttons that resemble those from GA::Generic::EditWin->addSimpleListButtons_hashIV
        my $button = $self->addButton($table, '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($table, '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub numbersTab_refreshList {

        # Called by $self->numbersTab to refresh the GA::Gtk::Simple::List, 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::Gtk::Simple::List
        #   $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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Time', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);


        # Make a local copy of the dictionary's time hashes
        %timeHash = $self->getEditHash_hashIV('timeHash');
        %timePluralHash = $self->getEditHash_hashIV('timePluralHash');

        # Time units
        $self->addLabel($table, '<b>Time units</b>',
            0, 4, 0, 1);
        $self->addLabel($table, '<i>Singular</i>',
            4, 8, 0, 1);
        $self->addLabel($table, '<i>Plural</i>',
            8, 12, 0, 1);

        $self->addLabel($table, 'Seconds',
            1, 4, 1, 2);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'second',
            4, 8, 1, 2);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'second',
            8, 12, 1, 2);

        $self->addLabel($table, 'Minutes',
            1, 4, 2, 3);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'minute',
            4, 8, 2, 3);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'minute',
            8, 12, 2, 3);

        $self->addLabel($table, 'Hours',
            1, 4, 3, 4);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'hour',
            4, 8, 3, 4);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'hour',
            8, 12, 3, 4);

        $self->addLabel($table, 'Days',
            1, 4, 4, 5);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'day',
            4, 8, 4, 5);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'day',
            8, 12, 4, 5);

        $self->addLabel($table, 'Weeks',
            1, 4, 5, 6);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'week',
            4, 8, 5, 6);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'week',
            8, 12, 5, 6);

        $self->addLabel($table, 'Months',
            1, 4, 6, 7);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'month',
            4, 8, 6, 7);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'month',
            8, 12, 6, 7);

        $self->addLabel($table, 'Years',
            1, 4, 7, 8);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'year',
            4, 8, 7, 8);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'year',
            8, 12, 7, 8);

        $self->addLabel($table, 'Decades',
            1, 4, 8, 9);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'decade',
            4, 8, 8, 9);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'decade',
            8, 12, 8, 9);

        $self->addLabel($table, 'Centuries',
            1, 4, 9, 10);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'century',
            4, 8, 9, 10);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'century',
            8, 12, 9, 10);

        $self->addLabel($table, 'Millennia',
            1, 4, 10, 11);
        $self->time1Tab_addEntryWithIcon($table, 'timeHash', 'millennium',
            4, 8, 10, 11);
        $self->time1Tab_addEntryWithIcon($table, 'timePluralHash', 'millennium',
            8, 12, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub time1Tab_addEntryWithIcon {

        # Adds a Gtk2::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
        #   $table      - The Gtk2::Table 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 table
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise returns the Gtk2::Entry created

        my (
            $self, $table, $iv, $key, $leftAttach, $rightAttach, $topAttach, $bottomAttach, $check
        ) = @_;

        # Local variables
        my %ivHash;

        # Check for improper arguments
        if (
            ! defined $table || ! 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 = Gtk2::Entry->new();

        # Display the existing value of the IV
        if (defined $self->editObj->$iv) {

            $entry->append_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 table
        $table->attach_defaults($entry, $leftAttach, $rightAttach, $topAttach, $bottomAttach);

        return $entry;
    }

    sub time2Tab {

        # Time2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Clock patterns - time of day
        $self->addLabel($table, '<b>Clock patterns - time of day</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Value:',
            7, 10, 8, 9);
        @comboList = (0, 1);
        my $combo = $self->addComboBox($table, 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($table, '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($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub time3Tab {

        # Time3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Clock patterns - hours
        $self->addLabel($table, '<b>Clock patterns - hours</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8, -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Value',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add buttons that resemble those from GA::Generic::EditWin->addSimpleListButtons_hashIV
        my $button = $self->addButton($table, '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($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub time4Tab {

        # Time4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Clock patterns - minutes
        $self->addLabel($table, '<b>Clock patterns - minutes</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8, -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Value',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add buttons that resemble those created by
        #   GA::Generic::EditWin->addSimpleListButtons_hashIV
        my $button = $self->addButton($table, '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($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('N_ouns', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Sentients
        $self->addLabel($table, '<b>Sentients</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8, -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('sentient');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns2Tab {

        # Nouns2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Creatures
        $self->addLabel($table, '<b>Creatures</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8, -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('creature');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns3Tab {

        # Nouns3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Portable types
        $self->addLabel($table, '<b>Portable types</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Objects which can normally be picked up</i>',
            1, 8, 1, 2);
        my $textView = $self->addTextView($table, 'portableTypeList', TRUE,
            1, 12, 2, 5,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
            -1, 110);
        my $buffer = $textView->get_buffer();

        my $button = Gtk2::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')));
        });
        $self->tooltips->set_tip($button, 'Use the default list of Portable types');
        $table->attach_defaults($button, 8, 12, 1, 2);

        # Decoration types
        $self->addLabel($table, '<b>Decoration types</b>',
            0, 12, 5, 6);
        $self->addLabel($table, '<i>Objects which can\'t normally be picked up</i>',
            1, 8, 6, 7);
        my $textView2 = $self->addTextView($table, 'decorationTypeList', TRUE,
            1, 12, 7, 10,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
            -1, 110);
        my $buffer2 = $textView2->get_buffer();

        my $button2 = Gtk2::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')));
        });
        $self->tooltips->set_tip($button2, 'Use the default list of Decoration types');
        $table->attach_defaults($button2, 8, 12, 6, 7);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns4Tab {

        # Nouns4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 4', $innerNotebook);

        # Portables
        $self->addLabel($table, '<b>Portables</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);

        @comboList = $self->getEditHash_listIV('portableTypeList');
        my $comboBox = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns5Tab {

        # Nouns5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Decorations
        $self->addLabel($table, '<b>Decorations</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);

        @comboList = $self->getEditHash_listIV('decorationTypeList');
        my $comboBox = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns6Tab {

        # Nouns6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Guilds
        $self->addLabel($table, '<b>Guilds</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of guild words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);          # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('guild');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns7Tab {

        # Nouns7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Races
        $self->addLabel($table, '<b>Races</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of race words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('race');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns8Tab {

        # Nouns8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _8', $innerNotebook);

        # Weapons
        $self->addLabel($table, '<b>Weapons</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of weapon words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('weapon');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns9Tab {

        # Nouns9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _9', $innerNotebook);

        # Armour
        $self->addLabel($table, '<b>Armour</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of armour words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('armour');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nouns10Tab {

        # Nouns10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 1_0', $innerNotebook);

        # Garments
        $self->addLabel($table, '<b>Garments</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of garment words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, 0,
            10, 12, 8, 9);
        $entry2->set_text('garment');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Plurals', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Plural ending patterns
        $self->addLabel($table, '<b>Plural ending patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Plural pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Singular ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 0, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub plurals2Tab {

        # Plurals2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Singular ending patterns
        $self->addLabel($table, '<b>Singular ending patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Singular pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 0, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Plural ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub plurals3Tab {

        # Plurals3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Plural nouns
        $self->addLabel($table, '<b>Plural nouns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 13, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Singular noun',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Plural noun',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            10, 13, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('Adj_ectives', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Adjectives
        $self->addLabel($table, '<b>Adjectives</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of adjective words</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Word', 'text',
            'Type', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Word',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Type',
            7, 10, 8, 9);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);
        $entry2->set_text('adj');

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub adjectives2Tab {

        # Adjectives2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Declined adjective patterns
        $self->addLabel($table, '<b>Declined adjective patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Declined ending pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Undeclined ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub adjectives3Tab {

        # Adjectives3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Undeclined adjective patterns
        $self->addLabel($table, '<b>Undeclined adjective patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Undeclined ending pattern',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Declined ending',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub adjectives4Tab {

        # Adjectives4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Declined adjectives
        $self->addLabel($table, '<b>Declined adjectives</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 13, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Undeclined form',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Declined form',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            10, 13, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('P_seudo', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Pseudo nouns
        $self->addLabel($table, '<b>Pseudo nouns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pseudo noun',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 12, 8, 9);

        $self->addLabel($table, 'Replacement noun',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub pseudo2Tab {

        # Pseudo2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Pseudo objects
        $self->addLabel($table, '<b>Pseudo objects</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pseudo object',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 12, 8, 9);

        $self->addLabel($table, 'Replacement object (if any)',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 0, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub pseudo3Tab {

        # Pseudo3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Pseudo adjectives
        $self->addLabel($table, '<b>Pseudo adjectives</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pseudo adjective',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 12, 8, 9);

        $self->addLabel($table, 'Replacement adjective (if any)',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 0, undef,
            4, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Contents', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Death words
        $self->addLabel($table, '<b>Death words</b>',
            0, 6, 0, 2);
        $self->addLabel($table, '<i>List of words which mark an object as dead</i>',
            1, 6, 2, 4);
        my ($textView) = $self->addTextView($table, undef, TRUE,
            1, 6, 4, 6,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
            -1, 125);
        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($table, '<b>Ignore words</b>',
            0, 6, 6, 8);
        $self->addLabel($table,
            '<i>List of words that should be ignored (never added to the unknown words list)</i>',
            1, 6, 8, 10);
        my $textView2 = $self->addTextView($table, undef, TRUE,
            1, 6, 10, 12,
            TRUE, TRUE, TRUE, FALSE,  # Treat as list, remove empty lines, do remove whitespace
            -1, 125);
        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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub contents2Tab {

        # Contents2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Unknown words
        $self->addLabel($table, '<b>Unknown words</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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
        @columnList = (
            'Unknown word', 'text',
        );

        my $slWidget = Games::Axmud::Gtk::Simple::List->new(@columnList);
        $slWidget->get_selection->set_mode('multiple');

        # Make the simple list scrollable
        my $scroller = Gtk2::ScrolledWindow->new;
        $scroller->set_policy('automatic', 'automatic');
        $scroller->set_size_request(-1, 200);
        $scroller->add($slWidget);
        $table->attach_defaults($scroller, 1, 6, 2, 8);

        # Initialise the list. Import the IV
        %ivHash = $self->getEditHash_hashIV('unknownWordHash');
        @{$slWidget->{data}} = sort {lc($a) cmp lc($b)} (keys %ivHash);

        $self->contents2Tab_addButtons($table,
            $slWidget, 'Guild', 'Selected words are guilds', 'addword -g ', undef,
            6, 8, 2, 3);
        $self->contents2Tab_addButtons($table,
            $slWidget, 'Race', 'Selected words are races', 'addword -r ', undef,
            8, 10, 2, 3);
        $self->contents2Tab_addButtons($table,
            $slWidget, 'Weapon', 'Selected words are weapons', 'addword -w ', undef,
            10, 12, 2, 3);
        $self->contents2Tab_addButtons($table,
            $slWidget, 'Armour', 'Selected words are armours', 'addword -a ', undef,
            6, 8, 3, 4);
        $self->contents2Tab_addButtons($table,
            $slWidget, 'Garment', 'Selected words are garments', 'addword -e ', undef,
            8, 10, 3, 4);
        $self->contents2Tab_addButtons($table,
            $slWidget, 'Sentient', 'Selected words are sentient beings', 'addword -s ', undef,
            10, 12, 3, 4);
        $self->contents2Tab_addButtons($table,
            $slWidget, 'Creature', 'Selected words are non-sentient beings', 'addword -k ', undef,
            6, 8, 5, 6);
        $self->contents2Tab_addButtons($table,
            $slWidget, 'Adjective', 'Selected words are adjectives', 'addword -j ', undef,
            8, 10, 5, 6);
        $self->contents2Tab_addButtons($table,
            $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($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 9, 6, 7);

        $self->contents2Tab_addButtons($table,
            $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($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 9, 7, 8);

        $self->contents2Tab_addButtons($table,
            $slWidget, 'Decoration', 'Selected words are decorations', 'addword -d ', $comboBox2,
            9, 12, 7, 8);

        # Delete word
        $self->contents2Tab_addButtons($table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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::Gtk::Simple::List displayed in this tab
        #
        # Expected arguments
        #   $table      - The Gtk2::Table for the tab
        #   $slWidget   - The GA::Gtk::Simple::List 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 table
        #
        # Return values
        #   'undef' on improper arguments
        #   Otherwise the Gtk2::Button created created

        my (
            $self, $table, $slWidget, $title, $tooltips, $cmd, $widget, $leftAttach, $rightAttach,
            $topAttach, $bottomAttach, $check
        ) = @_;

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

        # Check for improper arguments
        if (
            ! defined $table || ! 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($table, $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Contents lines
        $self->addLabel($table, '<b>Contents lines</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 180);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        $self->addLabel($table, 'Replacement',
            1, 3, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 10, 11);

        # Add a checkbutton right at the bottom
        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            6, 7, 11, 12);
        $unknownFlag = FALSE;       # Button starts deselected
        $checkButton->signal_connect('toggled' => sub {

            if ($checkButton->get_active()) {
                $unknownFlag = TRUE;
            } else {
                $unknownFlag = FALSE;
            }
        });
        $self->addLabel($table, 'Also add the replacement as unknown word(s)',
            7, 12, 11, 12);

        # Add buttons
        my $button = $self->addButton($table,
            'Use selected', 'Copy the selected contents line into the entry box below', undef,
            1, 3, 8, 9);
        $button->signal_connect('clicked' => sub {

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

                $entry->set_text($key);
                $entry2->set_text('');
            }
        });

        my $button2 = $self->addButton($table,
            'Delete selected', 'Delete the selected contents line', undef,
            8, 10, 8, 9);
        $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);
            }
        });

        my $button3 = $self->addButton($table,
            'Delete all', 'Empty the hash which stores contents lines', undef,
            10, 12, 8, 9);
        $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);
        });

        my $button4 = $self->addButton($table,
            'Add pseudo-noun', 'Add the pattern and replacement as a pseudo-noun', undef,
            6, 8, 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($table,
            'Add pseudo-obj', 'Add the pattern and replacement as a pseudo-object', undef,
            8, 10, 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 $button6 = $self->addButton($table,
            'Add pseudo-adj', 'Add the pattern and replacement as a pseudo-adjective', undef,
            10, 12, 10, 11,
            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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub contents3Tab_refreshList {

        # Called by $self->contentsTab to refresh the GA::Gtk::Simple::List, displaying only keys
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Directions', $self->notebook);

        # Add tabs to the inner notebook
        $self->directions1Tab($innerNotebook);
        $self->directions2Tab($innerNotebook);
        $self->directions3Tab($innerNotebook);
        $self->directions4Tab($innerNotebook);

        return 1;
    }

    sub directions1Tab {

        # Directions1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Primary directions
        $self->addLabel($table, '<b>Primary directions</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of the standard compass directions, plus up/down</i>',
            1, 6, 1, 2);

        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            6, 7, 1, 2);
        # (->signal_connect appears below)
        $self->addLabel($table, '<i>Show all intercardinal directions</i>',
            7, 12, 1, 2);

        # Create the simple list
        @columnList = (
            'Standard', 'text',
            'Custom', 'text',
            'Abbrev\'n', 'text',
            'Opposite dirs', 'text',
            'Opp abbrev\'d dirs', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 7,
            -1, 160);      # Fixed height

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

        # Entry boxes
        $self->addLabel($table, 'Map direction',
            1, 4, 8, 9);
        my $entry = $self->addEntry($table, undef, FALSE,
            4, 6, 8, 9);

        $self->addLabel($table, 'Direction',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            9, 12, 8, 9);

        $self->addLabel($table, 'Abbreviation',
            1, 4, 9, 10);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            4, 6, 9, 10);

        $self->addLabel($table, 'Opposite dirs',
            6, 9, 9, 10);
        my $entry4 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            9, 12, 9, 10);

        $self->addLabel($table, 'Opposite abbrev dirs',
            1, 4, 10, 11);
        my $entry5 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            4, 6, 10, 11);

        # 'Edit direction' button
        my $button = $self->addButton($table,
            '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($table,
            '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,
                %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;
                }

                # (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
                }

                # 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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub directions1Tab_refreshList {

        # Called by $self->directions1Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget       - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Primary directions
        $self->addLabel($table, '<b>Secondary directions</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 160);      # Fixed height

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

        # Entry/combo boxes
        $self->addLabel($table, 'Direction',
            1, 2, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 6, 9, 10);

        $self->addLabel($table, 'Abbreviation',
            6, 8, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            8, 12, 9, 10);

        $self->addLabel($table, 'Opposite dirs',
            1, 2, 10, 11);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            2, 6, 10, 11);

        $self->addLabel($table, 'Opposite abbrev dirs',
            6, 8, 10, 11);
        my $entry4 = $self->addEntryWithIcon($table, 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($table, 'Auto-allocate to',
            1, 2, 11, 12);
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            2, 6, 11, 12);

        # 'Unselect' button
        my $button = $self->addButton($table,
            '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($table,
            'Add/Edit direction', '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($table,
            'Delete direction', '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($table,
            '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($table,
            '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($table,
            '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, %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;
                }

                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
                    }
                }

                # 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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub directions2Tab_refreshList {

        # Called by $self->directions2Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 . '->directions3Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Speedwalking characters
        $self->addLabel($table, '<b>Speedwalking characters</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10, -1, 220);      # Fixed height

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

        # Editing widgets
        $self->addLabel($table, 'Speedwalking character (a-z)',
            1, 4, 10, 11);
        my $entry = $self->addEntryWithIcon(
            $table,
            undef,
            \&directions3Tab_checkEntry,
            undef,
            undef,
            4, 6, 10, 11,
            1, 1);      # Max width
        $self->addLabel($table, 'Movement command',
            6, 8, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            8, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub directions3Tab_checkEntry {

        # Called by $self->directions3Tab to check the text in the Gtk2::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk2::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 . '->directions3Tab_checkEntry',
                @_,
            );
        }

#        if ($text =~ m/^[A-Za-z]$/) {
        if ($text =~ m/^[[:alpha:]]$/) {
            return 1;
        } else {
            return undef;
        }
    }

    sub directions4Tab {

        # Directions4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->directions4Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Get the current highest-priority command cage
        $cageObj = $self->session->findHighestCage('cmd');

        # Speedwalking modifier characters
        $self->addLabel($table, '<b>Speedwalking modifier characters</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Editing widgets
        $self->addLabel($table, 'Modifier character (A-Z)',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon(
            $table,
            undef,
            \&directions3Tab_checkEntry,
            undef,
            undef,
            4, 6, 8, 9,
            1, 1);      # Max width

        $self->addLabel($table, '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($table, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            8, 12, 8, 9);
        # (->signal_connect appears below)

        $self->addLabel($table, 'Current replacement command',
            1, 4, 9, 10);
        my $entry2 = $self->addEntry($table, 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(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 {}        # 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->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');
            }

            # 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 object 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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>General properties</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Exit model #',
            1, 3, 2, 3);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Parent room #',
            1, 3, 3, 4);
        $self->addEntry($table, 'parent', FALSE,
            3, 6, 3, 4);

        $self->addLabel($table, 'Exit type',
            1, 3, 4, 5);
        $self->addEntry($table, 'exitType', FALSE,
            3, 6, 4, 5);

        $self->addLabel($table, 'Nominal direction',
            1, 3, 5, 6);
        $self->addEntry($table, 'dir', FALSE,
            3, 6, 5, 6);
        $self->addLabel($table, 'Mapped as direction',
            1, 3, 6, 7);
        my $entry = $self->addEntry($table, undef, FALSE,
            3, 6, 6, 7);
        if ($self->editObj->mapDir) {
            $entry->set_text($self->editObj->mapDir);
        } else {
            $entry->set_text('unallocatable');
        }
        $self->addLabel($table, 'Draw mode',
            1, 3, 7, 8);
        $self->addEntry($table, 'drawMode', FALSE,
            3, 6, 7, 8);
        $self->addLabel($table, 'Destination room #',
            1, 3, 8, 9);
        $self->addEntry($table, 'destRoom', FALSE,
            3, 6, 8, 9);
        $self->addLabel($table, 'Twin exit #',
            1, 3, 9, 10);
        $self->addEntry($table, 'twinExit', FALSE,
            3, 6, 9, 10);
        $self->addLabel($table, 'Shadow exit #',
            1, 3, 10, 11);
        $self->addEntry($table, 'shadowExit', FALSE,
            3, 6, 10, 11);


        # Right column
        $self->addLabel($table, 'Broken exit flag',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'brokenFlag', FALSE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Bent broken exit flag',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'bentFlag', FALSE,
            11, 12, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Region exit flag',
            7, 11, 3, 4);
        $self->addCheckButton($table, 'regionFlag', FALSE,
            11, 12, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Region super-exit flag',
            7, 11, 4, 5);
        $self->addCheckButton($table, 'superFlag', FALSE,
            11, 12, 4, 5, 1, 0.5);
        $self->addLabel($table, 'One-way exit flag',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'oneWayFlag', FALSE,
            11, 12, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Incoming 1-way dir',
            7, 11, 6, 7);
        $self->addEntry($table, 'oneWayDir', FALSE,
            9, 12, 6, 7);
        $self->addLabel($table, 'Retracing exit flag',
            7, 11, 7, 8);
        $self->addCheckButton($table, 'retraceFlag', FALSE,
            11, 12, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Exit tag',
            7, 9, 8, 9);
        $self->addEntry($table, 'exitTag', FALSE,
            9, 12, 8, 9);
        $self->addLabel($table, 'Exit tag X offset',
            7, 9, 9, 10);
        $self->addEntry($table, 'exitTagXOffset', FALSE,
            9, 12, 9, 10);
        $self->addLabel($table, 'Exit tag Y offset',
            7, 9, 10, 11);
        $self->addEntry($table, 'exitTagYOffset', FALSE,
            9, 12, 10, 11);

        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
        my ($vBox, $table) = $self->addTab('_Assisted moves', $self->notebook);

        # Assisted moves
        $self->addLabel($table, '<b>Assisted moves</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

        # 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($table, 'Profile',
            1, 3, 8, 9);
        my $combo = $self->addComboBox($table, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($table, 'Command sequence',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Doors', $self->notebook);

        # Door commands
        $self->addLabel($table, '<b>Door commands</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding world model objects
        $self->addLabel($table, 'Profile',
            1, 3, 8, 9);
        @comboList = qw(break pick unlock open close lock);
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($table, 'Command sequence',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Random', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Random exit type</b>',
            0, 6, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'none\'', 'randomType',
            'none',         # IV set to this value when toggled
            TRUE,           # Sensitised widget
            1, 3, 1, 2);
        $self->addLabel($table, 'Not a random exit',
            3, 6, 1, 2);

        # Right column
        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'same_region\'', 'randomType', 'same_region', TRUE,
            7, 9, 1, 2);
        $self->addLabel($table, 'Leads to random room in same region',
            9, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'any_region\'', 'randomType', 'any_region', TRUE,
            7, 9, 2, 3);
        $self->addLabel($table, 'Leads to random room anywhere',
            9, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'temp_region\'', 'randomType', 'temp_region', TRUE,
            7, 9, 3, 4);
        $self->addLabel($table, 'Creates new room in temporary region',
            9, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'room_list\'', 'randomType', 'room_list', TRUE,
            7, 9, 4, 5);
        $self->addLabel($table, 'Leads to random room from this list:',
            9, 12, 4, 5);

        $self->addTextView($table, 'randomDestList', TRUE,
            9, 12, 5, 11,
            undef, undef, undef, undef,
            -1, 220);               # Fixed width/height

        $self->addLabel($table, '<i>(Add a list of room numbers, one per line)</i>',
            9, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('Orna_ments', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Exit ornaments</b>',
            0, 6, 0, 1);

        $self->addLabel($table, 'Breakable door',
            1, 5, 1, 2);
        my $button = $self->addCheckButton($table, undef, FALSE,
            5, 6, 1, 2);
        $self->addLabel($table, 'Pickable lock',
            1, 5, 2, 3);
        my $button2 = $self->addCheckButton($table, undef, FALSE,
            5, 6, 2, 3);
        $self->addLabel($table, 'Lockable door',
            1, 5, 3, 4);
        my $button3 = $self->addCheckButton($table, undef, FALSE,
            5, 6, 3, 4);
        $self->addLabel($table, 'Openable door',
            1, 5, 4, 5);
        my $button4 = $self->addCheckButton($table, undef, FALSE,
            5, 6, 4, 5);
        $self->addLabel($table, 'Exit impassable',
            1, 5, 5, 6);
        my $button5 = $self->addCheckButton($table, undef, FALSE,
            5, 6, 5, 6);

        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);
        }

        $self->addLabel($table, '<b>Hidden exits</b>',
            0, 6, 6, 7);
        $self->addLabel($table, 'Exit is hidden',
            1, 5, 7, 8);
        $self->addCheckButton($table, 'hiddenFlag', FALSE,
            5, 6, 7, 8);

        $self->addLabel(
            $table,
            '<b>Exit information</b>',
            0, 6, 8, 9);
        $self->addLabel(
            $table,
            '<i>(e.g. further details about the destination room)</i>',
            1, 6, 9, 10);
        $self->addEntryWithButton($table, 'exitInfo', TRUE,
            1, 6, 10, 11);

        # Right column
        $self->addLabel($table, '<b>Exit current state</b>',
            6, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'normal\'', 'exitState',
            'normal',              # IV set to this value when toggled
            FALSE,          # Insensitive widget
            7, 9, 1, 2);
        $self->addLabel($table, 'Exit passable/state unknown',
            9, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'open\'', 'exitState', 'open', FALSE,
            7, 9, 2, 3);
        $self->addLabel($table, 'Exit open',
            9, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'closed\'', 'exitState', 'closed', FALSE,
            7, 9, 3, 4);
        $self->addLabel($table, 'Exit closed',
            9, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'locked\'', 'exitState', 'locked', FALSE,
            7, 9, 4, 5);
        $self->addLabel($table, 'Exit locked',
            9, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'secret\'', 'exitState', 'secret', FALSE,
            7, 9, 5, 6);
        $self->addLabel($table, 'Secret exit',
            9, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'secret_open\'', 'exitState', 'secret_open', FALSE,
            7, 9, 6, 7);
        $self->addLabel($table, 'Secret open exit',
            9, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'secret_closed\'', 'exitState', 'secret_closed', FALSE,
            7, 9, 7, 8);
        $self->addLabel($table, 'Secret closed exit',
            9, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'secret_locked\'', 'exitState', 'secret_locked', FALSE,
            7, 9, 8, 9);
        $self->addLabel($table, 'Secret locked exit',
            9, 12, 8, 9);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'impass\'', 'exitState', 'impass', FALSE,
            7, 9, 9, 10);
        $self->addLabel($table, 'Exit impassable',
            9, 12, 9, 10);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'dark\'', 'exitState', 'dark', FALSE,
            7, 9, 10, 11);
        $self->addLabel($table, 'Destination room is dark',
            9, 12, 10, 11);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'danger\'', 'exitState', 'danger', FALSE,
            7, 9, 11, 12);
        $self->addLabel($table, 'Destination room is dangerous',
            9, 12, 11, 12);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'other\'', 'exitState', 'other', FALSE,
            7, 9, 12, 13);
        $self->addLabel($table, 'Other state',
            9, 12, 12, 13);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 table size
        my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # 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($table);

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 (%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) {

            # 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 object 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
        #   $table  -   The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $interfaceModelObj,
            @comboList,
        );

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Get the corresponding interface model object
        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', $self->editObj->category);

        # Standard interface attributes
        $self->addLabel($table, '<b>Standard interface attributes</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Category',
            1, 3, 2, 3);
        $self->addEntry($table, 'category', FALSE,
            3, 6, 2, 3);

        $self->addLabel($table, 'Enabled',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'enabledFlag', TRUE,
            11, 12, 1, 2);

        $self->addLabel($table, 'Stimulus <i>(' . $interfaceModelObj->stimulusName . ')</i>',
            1, 3, 3, 4);
        # For hooks, use a combo; for everything else, use an entry
        if ($self->editObj->category eq 'hook') {

            @comboList = sort {$a cmp $b} ($interfaceModelObj->ivKeys('hookEventHash'));
            $self->addComboBox($table, 'stimulus', \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 3, 4);

        } else {

            $self->addEntryWithIcon($table, 'stimulus', 'string', 1, undef,
                3, 12, 3, 4);
        }

        # For triggers, the response can be any of 'instruction', 'pattern', 'substitution'
        $self->addLabel($table, 'Response <i>(' . $interfaceModelObj->responseName . ')</i>',
            1, 3, 4, 5);
        $self->addEntryWithIcon($table, 'response', 'string', 1, undef,
            3, 12, 4, 5);
        if ($interfaceModelObj->category eq 'trigger') {

            $self->addLabel(
                $table,
                '<i>NB Splitter triggers: the response is a pattern.</i>',
                3, 12, 5, 6);
            $self->addLabel(
                $table,
                '<i>NB Rewriter triggers: the response is a substitution</i>',
                3, 12, 6, 7);
        }

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('_Before / after', $self->notebook);

        $single = $self->editObj->category;
        if ($single eq 'alias') {
            $plural = 'aliases';
        } else {
            $plural = $single . 's';
        }

        # Door patterns
        $self->addLabel($table, '<b>Before ' . $plural . '</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table,
            '<i>When this ' . $single . ' becomes active, it is tested before the following '
            . $plural . '</i>',
            1, 12, 2, 4);
        $self->beforeAfterTab_addTextView($table, 'before', 4);

        # Locked door patterns
        $self->addLabel($table, '<b>After ' . $plural . '</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $table,
            '<i>When this ' . $single . ' becomes active, it is tested after the following '
            . $plural . '</i>',
            1, 12, 8, 10);
        $self->beforeAfterTab_addTextView($table, 'after', 10);

        # (Explanatory message for triggers only)
        if ($single eq 'trigger') {

            $self->addLabel(
                $table,
                '<i>(NB All splitter triggers are tested before any other kind of trigger is'
                . ' tested)</i>',
                1, 12, 12, 14);
        }

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub beforeAfterTab_addTextView {

        # Called by $self->beforeAfterTab to create a Gtk2::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
        #   $table      - The Gtk2::Table
        #   $type       - 'before' or 'after'
        #   $row        - The Gtk2::Table row where the textview is drawn
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $table, $type, $row, $check) = @_;

        # Local variables
        my @sortedList;

        # Check for improper arguments
        if (! defined $table || ! defined $type || ! defined $row || defined $check) {

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

        # Create the textview
        my $textView = $self->addTextView($table, undef, TRUE,
            1, 12, $row, ($row + 2),
            TRUE, TRUE, TRUE, FALSE,    # Treat as list, remove empty lines, do remove whitespace
            -1, 140);                   # Fixed height
        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::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 table size
        my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # 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($table);

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 object 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
        #   $table  - The Gtk2::Table
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $interfaceModelObj,
            @comboList,
        );

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Settings (left column)
        $self->addLabel($table, '<b>Settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 12, 1, 2);
        $self->addLabel($table, 'Number',
            1, 3, 2, 3);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Category',
            1, 3, 3, 4);
        $self->addEntry($table, 'category', FALSE,
            3, 6, 3, 4);

        # (Right column)
        $self->addLabel($table, '(Inactive) parent',
            7, 9, 2, 3);
        my $entry = $self->addEntry($table, 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($table, 'Type',
            7, 9, 3, 4);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            9, 12, 3, 4);
        if ($self->editObj->indepFlag) {
            $entry2->set_text('independent');
        } else {
            $entry2->set_text('dependent');
        }

        # Standard attributes
        $self->addLabel($table, '<b>Standard attributes</b>',
            0, 12, 4, 5);
        $interfaceModelObj = $axmud::CLIENT->ivShow('interfaceModelHash', $self->editObj->category);

        $self->addLabel($table, '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 'hook') {

            @comboList = sort {$a cmp $b} ($interfaceModelObj->ivKeys('hookEventHash'));
            $self->addComboBox($table, 'stimulus', \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 5, 6);

        } else {

            $self->addEntryWithIcon($table, 'stimulus', 'string', 1, undef,
                3, 12, 5, 6);
        }

        $self->addLabel($table, 'Response <i>(' . $interfaceModelObj->responseName . ')</i>',
            1, 3, 6, 7);
        $self->addEntryWithIcon($table, 'response', 'string', 1, undef,
            3, 12, 6, 7);
        $self->addLabel($table, 'Enabled',
            1, 11, 7, 8);
        $self->addCheckButton($table, 'enabledFlag', TRUE,
            11, 12, 7, 8);

        # Independent interface IVs
        $self->addLabel($table, '<b>Independent interface IVs</b>',
            0, 6, 8, 9);
        $self->addLabel($table, 'Associated profile',
            1, 3, 9, 10);
        $self->addEntry($table, 'assocProf', FALSE,
            3, 6, 9, 10);
        $self->addLabel($table, 'Profile category',
            1, 3, 10, 11);
        $self->addEntry($table, 'assocProfCategory', FALSE,
            3, 6, 10, 11);

        # Dependent interface IVs
        $self->addLabel($table, '<b>Dependent interface IVs</b>',
            7, 12, 8, 9);
        $self->addLabel($table, 'Call class',
            7, 9, 9, 10);
        $self->addEntry($table, 'callClass', 0,
            9, 12, 9, 10);
        $self->addLabel($table, '(...named)',
            7, 9, 10, 11);
        my $entry3 = $self->addEntry($table, undef, 0,
            9, 12, 10, 11);
        if (defined $self->editObj->callClass) {

            $entry3->set_text($self->editObj->callClass->_objName);
        }
        $self->addLabel($table, 'Call method',
            7, 9, 11, 12);
        $self->addEntry($table, 'callMethod', 0,
            9, 12, 11, 12);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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
        my ($vBox, $table) = $self->addTab('_Attributes', $self->notebook);

        # Attributes
        $self->addLabel($table, '<b>Attributes</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 250);     # Fixed height

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

        # Add a single button
        my $button = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Properties', $self->notebook);

        # Quest solution
        $self->addLabel($table, '<b>Properties</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

        # Add a single button
        my $button = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 table size
        my ($vBox, $table) = $self->addTab('_Category', $self->notebook);

        # Set up the rest of the tab
        $self->categoryTab($table);

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @eventList;

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Interface category</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Category',
            1, 3, 1, 2);
        $self->addEntry($table, 'category', FALSE,
            3, 6, 1, 2);

        # Right column
        $self->addLabel($table, '<b>' . ucfirst($self->editObj->category) . ' attributes</b>',
            7, 12, 0, 1);
        $self->addLabel($table, 'Stimulus name',
            7, 9, 1, 2);
        $self->addEntry($table, 'stimulusName', FALSE,
            9, 12, 1, 2);
        $self->addLabel($table, 'Response name',
            7, 9, 2, 3);
        $self->addEntry($table, 'responseName', FALSE,
            9, 12, 2, 3);

        # Hook events (for the hook interface model object only)
        if ($self->editObj->category eq 'hook') {

            # Hook events
            $self->addLabel($table, '<b>List of hook events</b>',
                0, 12, 3, 4);
            my $textView = $self->addTextView($table, undef, FALSE,
               1, 12, 4, 12,
               undef, undef, undef, undef,
               -1, 250);        # Fixed height

            @eventList = sort {$a cmp $b} ($self->editObj->ivKeys('hookEventHash'));

            my $buffer = $textView->get_buffer();
            $buffer->set_text(join("\n", @eventList));
        }

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Attributes', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Quest solution
        $self->addLabel($table, '<b>Attribute types</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attributes2Tab {

        # Attributes2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Quest solution
        $self->addLabel($table, '<b>Default values</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attributes3Tab {

        # Attributes3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Quest solution
        $self->addLabel($table, '<b>Compulsory switches</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attributes4Tab {

        # Attributes4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Quest solution
        $self->addLabel($table, '<b>Default values</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

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

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

{ package Games::Axmud::EditWin::Keycode;

    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::Keycode')) {
            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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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->valuesTab();
        $self->alternativesTab();

        return 1;
    }

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

    # Notebook tabs

    sub nameTab {

        # Name tab - called by $self->setupNotebook
        #
        # Expected arguments
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Keycode object name
        $self->addLabel($table, '<b>Keycode object name</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Keycodes customised (not using defaults)',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'customisedFlag', FALSE,
            11, 12, 1, 2);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub valuesTab {

        # Values 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 . '->valuesTab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('_Values', $self->notebook);

        # Keycode values
        $self->addLabel($table, '<b>Keycode values</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of current and default values for each keycode</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Standard value', 'text',
            'Current value', 'text',
            'Default value', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

        # If the keycode object being edited is the current one...
        if ($self->editObj eq $axmud::CLIENT->currentKeycodeObj) {

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

            my $button = $self->addButton(
                $table,
                'Set new value',
                'Set the current value of the selected keycode',
                undef,
                6, 8, 10, 11,
                TRUE,               # Irreversible
            );
            $button->signal_connect('clicked' => sub {

                my ($keycode, $value);

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

                    # Get the new value of the selected keycode
                    $value = $entry->get_text();

                    # Set the new value
                    $self->session->pseudoCmd(
                        'setkeycode ' . $keycode . ' <' . $value . '>',
                        $self->pseudoCmdMode,
                    );

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

            my $button2 = $self->addButton(
                $table,
                'Use default',
                'Set the default value for the selected keycode',
                undef,
                8, 10, 10, 11,
                TRUE,               # Irreversible
            );
            $button2->signal_connect('clicked' => sub {

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

                    # Set the new value of the selected keycode to its default
                    $self->session->pseudoCmd(
                        'resetkeycode ' . $keyCode,
                        $self->pseudoCmdMode,
                    );

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

        } else {

            $self->addLabel($table,
                '<i>Keycode values can only be changed on the current keycode object</i>',
                1, 10, 10, 11);
        }

        my $button3 = $self->addButton(
            $table,
            'Dump',
            'Display keycode values in the \'main\' window',
            undef,
            10, 12, 10, 11,
        );
        $button3->signal_connect('clicked' => sub {

            # Display the values
            $self->session->pseudoCmd('listkeycode', $self->pseudoCmdMode);
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub valuesTab_refreshList {

        # Called by $self->valuesTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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,
            %keycodeHash, %constHash,
        );

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

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

        # Import IVs (for speed)
        %keycodeHash = $self->editObj->keycodeHash;
        %constHash = $axmud::CLIENT->constKeycodeHash;

        # Compile the simple list data (these IVs are not saved to $self->editHash)
        foreach my $keycode ($axmud::CLIENT->constKeycodeList) {

            push (@dataList,
                $keycode,
                $keycodeHash{$keycode},
                $constHash{$keycode},
            );
        }

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

        return 1;
    }

    sub alternativesTab {

        # Alternatives 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 . '->alternativesTab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('_Alternatives', $self->notebook);

        # Alternative keycode types
        $self->addLabel($table, '<b>Alternative keycode values</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>These alternatives can be used in place of standard keycode values</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Alternative keycode value', 'text',
            'Equivalent standard keycode value', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);     # Fixed height

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

        # Add edit buttons
        my $button = $self->addButton(
            $table,
            'Dump',
            'Display a list of alternative keycode types in the \'main\' window',
            undef,
            10, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Display the values
            $self->session->pseudoCmd('listkeycodealternative', $self->pseudoCmdMode);
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub alternativesTab_refreshList {

        # Called by $self->alternativesTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 . '->alternativesTab_refreshList',
                @_,
            );
        }

        # Import the hash of alternative forms of keycodes, and sort them
        %hash = $axmud::CLIENT->constAltKeycodeHash;
        @sortedList = sort {lc($a) cmp lc($b)} (keys %hash);

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

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

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

        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 ($vbox, $table) = $self->addTab('_Mask', $self->notebook);

        # Cage mask
        $self->addLabel($table, '<b>Cage mask</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);      # Fixed height

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

        # Add some buttons and entry boxes

        $self->addLabel($table, '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($table, '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($table, '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($table, '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($table, '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
        $vbox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub maskTab_refreshList {

        # Called by $self->maskTab to reset the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget       - The GA::Gtk::Simple::List
        #   $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::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 {}        # Inherited from GA::Generic::ConfigWin

#   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 object 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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Map label style
        $self->addLabel($table, '<b>Map label style</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Style name (max 16 chars)',
            1, 4, 1, 2);
        $self->addEntryWithIcon($table, 'name', 'string', 1, 16,
            4, 8, 1, 2,
            16, 16);

        $self->addLabel($table, 'Text colour',
            1, 4, 2, 3);
        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($table,
            $self->editObj->textColour,
            undef,                          # No neutral colour
            4, 6, 2, 3);
        my $entry = $self->addEntry($table, 'textColour', FALSE,
            6, 8, 2, 3,
            7, 7);
        my $button = $self->addButton($table, 'Set', 'Set this colour', undef,
            8, 10, 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($table, 'Reset', 'Reset this colour', undef,
            10, 12, 2, 3);
        $button2->signal_connect('clicked' => sub {

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

            # Update IVs
            $self->ivAdd('editHash', 'textColour', undef);
        });

        $self->addLabel($table, 'Underlay colour',
            1, 4, 3, 4);
        my ($frame2, $canvas2, $canvasObj2) = $self->addSimpleCanvas($table,
            $self->editObj->underlayColour,
            undef,                          # No neutral colour
            4, 6, 3, 4);
        my $entry2 = $self->addEntry($table, 'underlayColour', FALSE,
            6, 8, 3, 4,
            7, 7);
        my $button3 = $self->addButton($table, 'Set', 'Set this colour', undef,
            8, 10, 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($table, 'Reset', 'Reset this colour', undef,
            10, 12, 3, 4);
        $button4->signal_connect('clicked' => sub {

            $canvasObj2 = $self->fillSimpleCanvas($canvas2, $canvasObj2);
            $entry2->set_text('');

            # Update IVs
            $self->ivAdd('editHash', 'underlayColour', undef);
        });

        $self->addLabel($table, 'Relative size (0.5 - 10)',
            1, 4, 4, 5);
        $self->addEntryWithIcon($table, 'relSize', 'float', 0.5, 10,
            4, 8, 4, 5,
            8, 8);

        my $checkButton = $self->addCheckButton($table, 'italicsFlag', TRUE,
            1, 3, 5, 6,
            0, 0.5);
        $checkButton->set_label('Italics');
        my $checkButton2 = $self->addCheckButton($table, 'boldFlag', TRUE,
            3, 5, 5, 6,
            0, 0.5);
        $checkButton2->set_label('Bold');
        my $checkButton3 = $self->addCheckButton($table, 'underlineFlag', TRUE,
            5, 7, 5, 6,
            0, 0.5);
        $checkButton3->set_label('Underline');
        my $checkButton4 = $self->addCheckButton($table, 'strikeFlag', TRUE,
            7, 9, 5, 6,
            0, 0.5);
        $checkButton4->set_label('Strikethrough');
        my $checkButton5 = $self->addCheckButton($table, 'boxFlag', TRUE,
            9, 12, 5, 6,
            0, 0.5);
        $checkButton5->set_label('Draw box around label');

#        $self->addLabel($table, 'Orientation',
#            1, 4, 6, 7);
#
#        $count = -1;
#        @list = (
#            'south' => 'Normal',
#            'north' => 'Rotated 180 degrees',
#            'west'  => 'Rotated 90 degrees clockwise',
#            'east'  => 'Rotated 90 degrees anti-clockwise',
#        );
#
#        do {
#
#            my $gravity = shift @list;
#            my $descrip = shift @list;
#
#            push (@comboList, $descrip);
#            $descripHash{$descrip} = $gravity;
#
#            $count++;
#            if ($gravity eq $self->editObj->gravity) {
#
#                $index = $count;
#            }
#
#        } until (! @list);
#
#        my $combo = $self->addComboBox($table, undef, \@comboList, '',
#            TRUE,               # No 'undef' value used
#            4, 8, 6, 7,
#        );
#        $combo->set_active($index),
#        $combo->signal_connect('changed' => sub {
#
#            my $descrip = $combo->get_active_text();
#
#            $self->ivAdd('editHash', 'gravity', $descripHash{$combo->get_active_text});
#        });

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Name
        $self->addLabel($table, '<b>Name</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Name',
            1, 4, 1, 2);
        $self->addEntry($table, 'name', 0,
            4, 6, 1, 2);
        $self->addLabel($table, 'Description',
            1, 4, 2, 3);
        $self->addEntryWithIcon($table, 'descrip', 'string', 1, 64,
            4, 12, 2, 3);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Contents', $self->notebook);

        # Contents list
        $self->addLabel($table, '<b>Contents list</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>The contents of the mission; each line is a separate instruction</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'missionList', TRUE,
            1, 12, 4, 8,
            TRUE, FALSE, FALSE, FALSE,   # Treat as a list, don't remove empty lines or whitespace
            -1, 250);                    # Fixed height

        $self->addLabel($table, '<b>Commands:</b> ',
            1, 4, 8, 9);
        $self->addLabel($table,
            '<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, 8, 9);

        $self->addLabel($table, '<b>Breaks:</b> ',
            1, 4, 9, 10);
        $self->addLabel($table,
            '<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, 9, 10);

        $self->addLabel($table, '<b>Automated logins:</b> ',
            1, 4, 10, 11);
        $self->addLabel($table,
            '<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>'
            . '<b>c</b> <i>send combined command</i>',
            4, 12, 10, 11);
        $self->addLabel($table,
            '<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
        $vBox->pack_start($table, 0, 0, 0);

        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 {}        # 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();

        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 object 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 it currently visible
                $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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>General properties</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Category',
            1, 3, 2, 3);
        $self->addEntry($table, 'category', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'In world model',
            1, 5, 3, 4);
        $self->addCheckButton($table, 'modelFlag', FALSE,
            5, 6, 3, 4, 1, 0.5);
        $self->addLabel($table, 'World model #',
            1, 3, 4, 5);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 4, 5);

        $self->addLabel($table, '<b>Source code</b>',
            0, 12, 6, 7);
        if ($self->session->worldModelObj->mudlibPath) {

            $self->addLabel($table,
                '<i>The equivalent file in the mudlib, relative to '
                . $self->session->worldModelObj->mudlibPath . '</i>',
                1, 12, 7, 8);

        } else {

            $self->addLabel($table,
                '<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($table, 'sourceCodePath', TRUE,
            1, 12, 8, 9);

        my $button = $self->addButton($table,
            'View',
            'View this file',
            undef,
            9, 11, 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($table,
            'Edit',
            'Edit this file',
            undef,
            11, 12, 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($table, '<i>For rooms in virtual areas, the virtual area\'s file</i>',
                1, 12, 10, 11);
            my $entry2 = $self->addEntry($table, '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($table,
                '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->addLabel($table, 'Concrete (not abstract) object',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'concreteFlag', FALSE,
            11, 12, 1, 2);
        $self->addLabel($table, 'Alive',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'aliveFlag', FALSE,
            11, 12, 2, 3);
        $self->addLabel($table, 'Sentient',
            7, 11, 3, 4);
        $self->addCheckButton($table, 'sentientFlag', FALSE,
            11, 12, 3, 4);
        $self->addLabel($table, 'Portable (can be carried)',
            7, 11, 4, 5);
        $self->addCheckButton($table, 'portableFlag', FALSE,
            11, 12, 4, 5);
        $self->addLabel($table, 'Saleable (can be sold)',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'saleableFlag', FALSE,
            11, 12, 5, 6);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Family', $self->notebook);

        # Add tabs to the inner notebook
        $self->family1Tab($innerNotebook);
        $self->family2Tab($innerNotebook);

        return 1;
    }

    sub family1Tab {

        # Family1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Parent object
        $self->addLabel($table, '<b>Parent object</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 100);     # Fixed height - only one row

        # 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($table, '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($table, undef, $funcRef, undef, undef,
                4, 6, 10, 11);

            my $button = $self->addButton($table,
                '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(
            $table, '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($table,
                '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub family2Tab {

        # Family2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Child objects
        $self->addLabel($table, '<b>Child objects</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 220);     # Fixed height

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

        # Add editing buttons
        $self->addLabel($table, '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($table, undef, $funcRef, undef, undef,
            4, 6, 10, 11);

        my $button = $self->addButton($table,
            '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(
            $table, '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub family1Tab_refreshList {

        # Called by $self->family1Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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
        my ($vBox, $table) = $self->addTab('N_otes', $self->notebook);

        # Definite articles
        $self->addLabel($table, '<b>Notes</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Any additional information about this object you want to retain between'
            . ' sessions</i>',
            1, 12, 1, 2);

        my $textView = $self->addTextView($table, 'notesList', TRUE,
            1, 12, 2, 10,
            TRUE, FALSE, FALSE, FALSE,  # Treat as list, don't remove empty lines or whitespace
            -1, 300);                   # Fixed height
        my $buffer = $textView->get_buffer();

        # Add a button to clear the list
        my $button = $self->addButton($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Words', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Left column
        $self->addLabel($table, '<b>Words</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Main noun',
            1, 3, 1, 2);
        $self->addEntryWithIcon($table, 'noun', 'string', 1, undef,
            3, 6, 1, 2);
        $self->addLabel($table, 'Noun tag',
            1, 3, 2, 3);
        $self->addEntry($table, 'nounTag', FALSE,
            3, 6, 2, 3);

        $self->addLabel($table, 'Base string',
            1, 3, 3, 4);
        $self->addEntryWithButton($table, 'baseString', TRUE,
            3, 12, 3, 4);
        $self->addLabel($table, 'Description',
            1, 3, 4, 5);
        $self->addEntryWithButton($table, 'descrip', TRUE,
            3, 12, 4, 5);

        $self->addLabel($table, '<b>Inventory task properties</b>',
            0, 12, 5, 6);
        $self->addLabel($table, 'Container model number',
            1, 3, 6, 7);
        $self->addEntry($table, 'container', FALSE,
            3, 6, 6, 7);
        $self->addLabel($table, 'Inventory type',
            1, 3, 7, 8);
        $self->addEntry($table, 'inventoryType', FALSE,
            3, 6, 7, 8);

        # Right column
        $self->addLabel($table, 'Multiple',
            7, 9, 1, 2);
        $self->addEntryWithIcon($table, 'multiple', 'float', 0, undef,
            9, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub words2Tab {

        # Words2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Other nouns
        $self->addLabel($table, '<b>Other nouns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Other words for this object which are known to be nouns (besides the main one)</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'otherNounList', TRUE,
            1, 12, 4, 6);

        # Adjectives
        $self->addLabel($table, '<b>Adjectives</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Words for this object that are known to be adjectives</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'adjList', TRUE,
            1, 12, 10, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub words3Tab {

        # Words3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Pseudo adjectives
        $self->addLabel($table, '<b>Pseudo-adjectives</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Adjectives for this object which are linked to longer terms (e.g. \'big\' for'
            . ' \'big-looking\')</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'pseudoAdjList', TRUE,
            1, 12, 4, 6);

        # Root adjectives
        $self->addLabel($table, '<b>Root adjectives</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Root forms of declined adjectives (not found in English)</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'rootAdjList', TRUE,
            1, 12, 10, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub words4Tab {

        # Words4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Unknown words
        $self->addLabel($table, '<b>Unknown words</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Other words for this object which aren\'t known nouns or adjectives</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'unknownWordList', TRUE,
            1, 12, 4, 6);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Attacks', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Properties for use by your code during attacks</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Target status',
            1, 3, 1, 2);
        $self->addEntry($table, 'targetStatus', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Target (attack) type',
            1, 3, 2, 3);
        $self->addEntry($table, 'targetType', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Target path',
            1, 3, 3, 4);
        $self->addEntry($table, 'targetPath', FALSE,
            3, 6, 3, 4);
        $self->addLabel($table, 'Target room number',
            1, 3, 4, 5);
        $self->addEntry($table, 'targetRoomNum', FALSE,
            3, 6, 4, 5);

        # Right column
        $self->addLabel($table, 'Already attacked',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'alreadyAttackedFlag', FALSE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Explicit in description',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'explicitFlag', TRUE,
            11, 12, 2, 3, 1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Physical', $self->notebook);

        # Add tabs to the inner notebook
        $self->physical1Tab($innerNotebook);
        $self->physical2Tab($innerNotebook);

        return 1;
    }

    sub physical1Tab {

        # Physical1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Left column
        $self->addLabel($table, '<b>Physical properties</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Explicitly in description',
            1, 5, 1, 2);
        $self->addCheckButton($table, 'explicitFlag', TRUE,
            5, 6, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Weight (if known)',
            1, 3, 2, 3);
        $self->addEntryWithButton($table, 'weight', TRUE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Condition (0-100)',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'condition', 'int', 0, 100,
            3, 6, 3, 4, 4, 4);
        $self->addLabel($table, 'Condition change flag',
            1, 5, 4, 5);
        $self->addCheckButton($table, 'conditionChangeFlag', FALSE,
            5, 6, 4, 5, 1, 0.5);

        # Right column
        $self->addLabel($table, 'Fixable',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'fixableFlag', TRUE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Sellable',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'sellableFlag', TRUE,
            11, 12, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Value (when bought)',
            7, 9, 3, 4);
        $self->addEntryWithButton($table, 'buyValue', TRUE,
            9, 12, 3, 4);
        $self->addLabel($table, 'Value (when sold)',
            7, 9, 4, 5);
        $self->addEntryWithButton($table, 'sellValue', TRUE,
            9, 12, 4, 5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub physical2Tab {

        # Physical2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Bonus stats
        $self->addLabel($table, '<b>Bonus stats</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

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

        # Add entry boxes and edit buttons
        $self->addLabel($table, 'Stat',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Bonus/penalty',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'float', undef, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Exclusive', $self->notebook);

        # Exclusive profiles
        $self->addLabel($table, '<b>Exclusive profiles</b>',
            0, 6, 0, 1);
        $self->addLabel($table,
            'Only exclusive profiles (characters, races, guilds, etc) can use this object?',
            1, 11, 1, 2);
        $self->addCheckButton($table, 'exclusiveFlag', TRUE,
            11, 12, 1, 2, 1, 0.5);

        # Add a simple list
        @columnList = (
            'Exclusive profile', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 280);     # Fixed height

        # 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($table, 'Exclusive profile:',
            1, 3, 10, 11);
        my $comboBox = $self->addComboBox($table, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 10, 11);

        # Add buttons
        my $button = $self->addButton($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub exclusiveTab_refreshList {

        # Resets the simple list displayed by $self->exclusiveTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Left column
        $self->addLabel($table,
            '<b>Properties of ' . $self->editObj->category . ' model objects</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Guild',
            1, 3, 1, 2);
        @comboList = sort {lc($a) cmp lc($b)} ($self->session->currentDict->ivKeys('guildHash'));
        $self->addComboBox($table, 'guild', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 1, 2);

        $self->addLabel($table, 'Race',
            1, 3, 2, 3);
        @comboList = sort {lc($a) cmp lc($b)} ($self->session->currentDict->ivKeys('raceHash'));
        $self->addComboBox($table, 'race', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);

        # Right column
        $self->addLabel($table, 'Level (if known)',
            7, 10, 1, 2);
        $self->addEntryWithIcon($table, 'level', 'int', 0, undef,
            10, 12, 1, 2, 8, 8);
        $self->addLabel($table, '(not less than)',
            8, 10, 2, 3);
        $self->addEntryWithIcon($table, 'weakerLevel', 'int', 0, undef,
            10, 12, 2, 3, 8, 8);
        $self->addLabel($table, '(not more than)',
            8, 10, 3, 4);
        $self->addEntryWithIcon($table, 'strongerLevel', 'int', 0, undef,
            10, 12, 3, 4, 8, 8);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub sentient2Tab {

        # Sentient2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Inventory list
        $self->addLabel($table, '<b>Inventory list</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of things the ' . $self->editObj->category . ' typically carries in its'
            . ' inventory</i>',
            1, 12, 1, 2);

        $self->addTextView($table, 'inventoryList', TRUE,
            1, 12, 2, 12,
            TRUE, TRUE, TRUE, FALSE,    # Treat as list, remove empty lines, do remove whitespace
            -1, 300);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub sentient3Tab {

        # Sentient3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Cash list
        $self->addLabel($table, '<b>Cash list</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, 'cashList', \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

        # Add a button
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub sentient4Tab {

        # Sentient4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Left column
        $self->addLabel($table,
            '<b>Properties of ' . $self->editObj->category . ' model objects (continued)</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Ever initiated combat (unfriendly)',
            1, 5, 1, 2);
        $self->addCheckButton($table, 'unfriendlyFlag', TRUE,
            5, 6, 1, 2, 1, 0.5);

        $self->addLabel($table, 'Morality',
            1, 5, 2, 3);
        @comboList = ('good', 'neutral', 'evil');
        $self->addComboBox($table, 'morality', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 2, 3);

        $self->addLabel($table, 'Attached to quest',
            1, 3, 3, 4);
        @comboList2 = $self->session->currentWorld->ivKeys('questHash');
        $self->addComboBox($table, 'questName', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            3, 6, 3, 4);

        # Right column
        $self->addLabel($table, 'Tends to wander around',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'wanderFlag', TRUE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Ever fleed combat',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'fleeFlag', TRUE,
            11, 12, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Tends to flee combat quickly',
            7, 11, 3, 4);
        $self->addCheckButton($table, 'quickFleeFlag', TRUE,
            11, 12, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Should <b>NEVER</b> be attacked',
            7, 11, 4, 5);
        $self->addCheckButton($table, 'noAttackFlag', TRUE,
            11, 12, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Mercies opponents (doesn\'t kill)',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'mercyFlag', TRUE,
            11, 12, 5, 6, 1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub sentient5Tab {

        # Sentient5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Action properties
        $self->addLabel($table, '<b>Action properties</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Flag to be set if the ' . $self->editObj->category
            . ' has ever performed an action</i>',
            1, 10, 1, 2);
        $self->addCheckButton($table, 'actionFlag', TRUE,
            10, 12, 1, 2, 1, 0.5);

        $self->addLabel($table,
            '<i>List of strings received when the ' . $self->editObj->category
            . ' performs an action:</i>',
            1, 10, 2, 3);
        $self->addTextView($table, 'actionList', TRUE,
            1, 12, 3, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 250);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub sentient6Tab {

        # Sentient6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Conversation properties
        $self->addLabel($table, '<b>Conversation properties</b>',
            0, 12, 0, 1);

        $self->addLabel($table,
            '<i>Flag to be set if the ' . $self->editObj->category
            . ' has ever said anything</i>',
            1, 10, 1, 2);
        $self->addCheckButton($table, 'talkativeFlag', TRUE,
            10, 12, 1, 2, 1, 0.5);

        $self->addLabel($table,
            '<i>List of things spoken by the ' . $self->editObj->category . ':</i>',
            1, 10, 2, 3);
        $self->addTextView($table, 'talkList', TRUE,
            1, 12, 3, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 250);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub character1Tab {

        # Character1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Left column
        $self->addLabel($table, '<b>Properties of ' . $self->editObj->category
            . ' model objects</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Guild',
            1, 3, 1, 2);
        @comboList = $self->session->currentDict->ivKeys('guildHash');
        $self->addComboBox($table, 'guild', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 1, 2);

        $self->addLabel($table, 'Race',
            1, 3, 2, 3);
        @comboList2 = $self->session->currentDict->ivKeys('raceHash');
        $self->addComboBox($table, 'race', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);

        if ($self->editObj->category eq 'char') {

            # Characters
            $self->addLabel($table, 'Own character',
                1, 5, 3, 4);
            $self->addCheckButton($table, 'ownCharFlag', TRUE,
                5, 6, 3, 4, 1, 0.5);
            $self->addLabel($table, 'Owner',
                1, 3, 4, 5);
            $self->addEntryWithButton($table, 'owner', TRUE,
                3, 6, 4, 5);

            $self->addLabel($table, 'Mortal status',
                1, 3, 5, 6);
            @comboList3 = ('mortal', 'wiz', 'test');
            $self->addComboBox($table, 'mortalStatus', \@comboList3, '',
                TRUE,               # No 'undef' value used
                3, 6, 5, 6);

            $self->addLabel($table, 'Diplomatic status',
                1, 3, 6, 7);
            @comboList4 = ('friendly', 'neutral', 'hostile');
            $self->addComboBox($table, 'diplomaticStatus', \@comboList4, '',
                TRUE,               # No 'undef' value used
                3, 6, 6, 7);

            $self->addLabel($table, 'Ever attacked you',
                1, 5, 7, 8);
            $self->addCheckButton($table, 'grudgeFlag', TRUE,
                5, 6, 7, 8, 1, 0.5);
            $self->addLabel($table, 'Fight back if attacked by this',
                1, 5, 8, 9);
            $self->addCheckButton($table, 'fightBackFlag', TRUE,
                5, 6, 8, 9, 1, 0.5);

        } elsif ($self->editObj->category eq 'minion') {

            # Minions
            $self->addLabel($table, 'Minion cost',
                1, 3, 3, 4);
            $self->addEntryWithIcon($table, 'value', 'string', 0, undef,
                3, 6, 3, 4);
            $self->addLabel($table, 'Own minion',
                1, 5, 4, 5);
            $self->addCheckButton($table, 'ownMinionFlag', TRUE,
                5, 6, 4, 5, 1, 0.5);
        }

        # Right column
        $self->addLabel($table, 'Level (if known)',
            7, 10, 1, 2);
        $self->addEntryWithIcon($table, 'level', 'int', 0, undef,
            10, 12, 1, 2, 8, 8);
        $self->addLabel($table, '(not less than)',
            7, 10, 2, 3);
        $self->addEntryWithIcon($table, 'weakerLevel', 'int', 0, undef,
            10, 12, 2, 3, 8, 8);
        $self->addLabel($table, '(not more than)',
            7, 10, 3, 4);
        $self->addEntryWithIcon($table, 'strongerLevel', 'int', 0, undef,
            10, 12, 3, 4, 8, 8);

        if ($self->editObj->category eq 'char') {

            $self->addLabel($table, 'Total XP (if known)',
                7, 10, 4, 5);
            $self->addEntryWithButton($table, 'totalXP', 1,
                10, 12, 4, 5);
            $self->addLabel($table, 'Total QP (if known)',
                7, 10, 5, 6);
            $self->addEntryWithButton($table, 'totalQP', 1,
                10, 12, 5, 6);
        }

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Region', $self->notebook);

        $self->addLabel($table, '<b>General properties</b>',
            0, 6, 0, 1);

        # Temporary region
        $self->addLabel($table, 'Temporary region',
            1, 11, 1, 2);
        $self->addCheckButton($table, 'tempRegionFlag', FALSE,
            11, 12, 1, 2, 1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 {}          # Inherited from GA::Generic::ConfigWin

    # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Room', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Left column
        $self->addLabel($table,
            '<b>Properties of ' . $self->editObj->category . ' model objects</b>',
            0, 6, 0, 1);

        $self->addLabel($table, 'Room tag',
            1, 2, 1, 2);
        $self->addEntry($table, 'roomTag', FALSE,
            2, 6, 1, 2);
        $self->addLabel($table, 'Offset on map',
            1, 2, 2, 3);
        $self->addLabel($table, 'X:',
            2, 3, 2, 3);
        $self->addEntry($table, 'roomTagXOffset', FALSE,
            3, 4, 2, 3, 6, 6);
        $self->addLabel($table, 'Y:',
            4, 5, 2, 3);
        $self->addEntry($table, 'roomTagYOffset', FALSE,
            5, 6, 2, 3, 6, 6);

        $self->addLabel($table, 'Room guild',
            1, 2, 3, 4);
        $self->addEntry($table, 'roomGuild', FALSE,
            2, 6, 3, 4);
        $self->addLabel($table, 'Offset on map',
            1, 2, 4, 5);
        $self->addLabel($table, 'X:',
            2, 3, 4, 5);
        $self->addEntry($table, 'roomGuildXOffset', FALSE,
            3, 4, 4, 5, 6, 6);
        $self->addLabel($table, 'Y:',
            4, 5, 4, 5);
        $self->addEntry($table, 'roomGuildYOffset', FALSE,
            5, 6, 4, 5, 6, 6);

        # Axbasic list
        $self->addLabel($table, '<b>' . $axmud::BASIC_NAME . ' list</b>',
            0, 6, 5, 6);
        $self->addLabel(
            $table,
            '<i>List of ' . $axmud::BASIC_NAME . ' scripts run when the room is entered</i>',
            1, 6, 6, 7);
        $self->addTextView($table, 'arriveScriptList', TRUE,
            1, 6, 7, 12);

        # Right column
        $self->addLabel($table, '<b>Regionmap grid position</b>',
            7, 13, 0, 1);
        $self->addLabel($table, 'X block',
            8, 11, 1, 2);
        $self->addEntry($table, 'xPosBlocks', FALSE,
            11, 13, 1, 2, 4, 4);
        $self->addLabel($table, 'Y block',
            8, 11, 2, 3);
        $self->addEntry($table, 'yPosBlocks', FALSE,
            11, 13, 2, 3, 4, 4);
        $self->addLabel($table, 'Z block',
            8, 11, 3, 4);
        $self->addEntry($table, 'zPosBlocks', FALSE,
            11, 13, 3, 4, 4, 4);

        $self->addLabel($table, 'Ever matched with Locator',
            8, 12, 4, 5);
        $self->addCheckButton($table, 'everMatchedFlag', FALSE,
            12, 13, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Unspecified room description',
            8, 12, 5, 6);
        $self->addCheckButton($table, 'unspecifiedFlag', FALSE,
            12, 13, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Currently dark',
            8, 12, 6, 7);
        $self->addCheckButton($table, 'currentlyDarkFlag', FALSE,
            12, 13, 6, 7, 1, 0.5);

        $self->addLabel($table, '<b>Wilderness mode</b>',
            7, 13, 7, 8);
        my $entry = $self->addEntry($table, undef, FALSE,
            8, 13, 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 to make spacing right
        $self->addLabel($table, '',
            8, 13, 9, 10);
        $self->addLabel($table, '',
            8, 13, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub room2Tab {

        # Room2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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,
        );

        # 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;

        # Tab setup
        if (! $tabTitle) {

            $tabTitle = 'Page _2';      # This function wasn't called by GA::EditWin::Painter
        }

        my ($vBox, $table) = $self->addTab($tabTitle, $innerNotebook);

        # Room flags
        $self->addLabel($table, '<b>Room flags</b>',
            0, 6, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);     # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Room flag category (filter)',
            1, 4, 10, 11);
        @comboList = $axmud::CLIENT->constRoomFilterList;
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            4, 6, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($table, 'Room flag',
            6, 7, 10, 11);
        @comboList2
            = $self->session->worldModelObj->getRoomFlagsInFilter($combo->get_active_text());

        my $combo2 = $self->addComboBox($table, undef, \@comboList2, '',
            TRUE,              # 'undef' value not used
            7, 10, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($table, '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($table, $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($table, 'Last flag drawn',
            1, 3, 11, 12);
        $self->addEntry($table, 'lastRoomFlag', FALSE,
            3, 6, 11, 12);

        my $button = $self->addButton($table,
            '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));
            }
        });

        my $button2 = $self->addButton($table,
            '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));
            }
        });

        my $button3 = $self->addButton($table, '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));
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub room2Tab_refreshList {

        # Called by $self->room2Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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,
            @list, @sortedList, @dataList,
            %ivHash,
        );

        # Check for improper arguments
        if (! defined $slWidget || ! defined $columns || 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) {

            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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Room titles
        $self->addLabel($table, '<b>Room titles</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>List of room titles (brief descriptions) for this room</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'titleList', TRUE,
            1, 12, 4, 6);

        # Room commands
        $self->addLabel($table, '<b>Room commands</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>List of commands available in this room</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'roomCmdList', TRUE,
            1, 12, 10, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub room4Tab {

        # Room4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab($tabTitle, $innerNotebook);

        # Verbose descriptions
        $self->addLabel($table, '<b>Verbose descriptions</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Light status',
            1, 3, 8, 9);
        @comboList = $self->session->worldModelObj->lightStatusList;
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($table, 'Verbose description',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub room5Tab {

        # Room5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab($tabTitle, $innerNotebook);

        # Noun list
        $self->addLabel($table, '<b>Noun list</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>List of recognised nouns that appear in this room and its contents</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'nounList', TRUE,
            1, 12, 4, 6);

        # Adjective list
        $self->addLabel($table, '<b>Adjective list</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>List of recognised adjectives that appear in this room and its contents</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'adjList', TRUE,
            1, 12, 10, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub room6Tab {

        # Room6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Character visits
        $self->addLabel($table, '<b>Character visits</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

        # 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($table, 'Character',
            1, 3, 8, 9);
        my $combo = $self->addComboBox($table, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 8, 9);

        $self->addLabel($table, 'Number of visits',
            7, 9, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub room7Tab {

        # Room7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab($tabTitle, $innerNotebook);

        # Exclusive profiles
        $self->addLabel($table, '<b>Exclusive profiles</b>',
            0, 6, 0, 1);
        $self->addLabel($table,
            'Only exclusive profiles (characters, races, guilds, etc) can enter this room?',
            1, 11, 1, 2);
        $self->addCheckButton($table, 'exclusiveFlag', TRUE,
            11, 12, 1, 2, 1, 0.5);

        # Add a simple list
        @columnList = (
            'Exclusive profile', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 250);     # Fixed height

        # 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($table, 'Exclusive profile:',
            1, 3, 10, 11);
        my $comboBox = $self->addComboBox($table, undef, \@sortedList, '',
            TRUE,               # No 'undef' value used
            3, 6, 10, 11);

        # Add buttons
        my $button = $self->addButton($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Exits', $self->notebook);

        # 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);

        return 1;
    }

    sub exits1Tab {

        # Exits1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Failed exit patterns
        $self->addLabel($table, '<b>Failed exit patterns</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table,
            '<i>Patterns which mean an exit in this room is (temporarily) unavailable</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'failExitPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                   # Fixed height
        # Special departure patterns
        $self->addLabel($table, '<b>Special departure patterns</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $table,
            '<i>Patterns which mean we\'ve left the room (when a new room statement isn\'t'
            . ' sent)</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'specialDepartPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub exits2Tab {

        # Exits2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->exits2Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Failed exit patterns
        $self->addLabel($table, '<b>Repulse exit patterns</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table,
            '<i>Patterns which mean the character has left the room involuntarily after a failed'
            . ' move</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'repulseExitPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                   # Fixed height

        # Involuntary exit patterns
        $self->addLabel($table, '<b>Involuntary exit patterns</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $table,
            '<i>Patterns which mean the character has left the room involuntarily</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'involuntaryExitPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub exits3Tab {

        # Exits3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Exit objects
        $self->addLabel($table, '<b>Exit objects</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);     # Fixed height

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

        # Add entry box for adding new exit objects
        $self->addLabel($table, 'Direction',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 10, 11);

        # Add editing buttons
        my $button = $self->addButton(
            $table,
            '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->exits3Tab_refreshList($slWidget, scalar (@columnList / 2));
            }
        });

        my $button2 = $self->addButton(
            $table,
            '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->exits3Tab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button3 = $self->addButton(
            $table,
            '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->exits3Tab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button4 = $self->addButton(
            $table,
            '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->exits3Tab_refreshList($slWidget, scalar (@columnList / 2));
                $self->resetEntryBoxes($entry);
            }
        });

        my $button5 = $self->addButton(
            $table,
            '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->exits3Tab_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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub exits3Tab_refreshList {

        # Called by $self->exits3Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 . '->exits3Tab_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 exits4Tab {

        # Exits4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Incoming uncertain exits
        $self->addLabel($table, '<b>Incoming uncertain exits</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of uncertain exits belonging to other rooms which point to this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Other room #', 'text',
            'Region', 'text',
            'Exit #', 'text',
            'Direction', 'text',
            'Map direction', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);     # Fixed height

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

        # Add a single button
        my $button = $self->addButton(
            $table,
            'Refresh list',
            'Refresh the list of incoming uncertain exits',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->exits4Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'uncertainExitHash',
            );
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub exits4Tab_refreshList {

        # Called by $self->exits4Tab, ->exits5Tab and ->exits6Tab to refresh the
        #   GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 . '->exits4Tab_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 exits5Tab {

        # Exits5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Incoming one-way exits
        $self->addLabel($table, '<b>Incoming one-way exits</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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 = (
            'Other room #', 'text',
            'Region', 'text',
            'Exit #', 'text',
            'Direction', 'text',
            'Map direction', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);     # Fixed height

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

        # Add a single button
        my $button = $self->addButton(
            $table,
            '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->exits4Tab_refreshList($slWidget, scalar (@columnList / 2), 'oneWayExitHash');
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub exits6Tab {

        # Exits6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Incoming random exits
        $self->addLabel($table, '<b>Incoming random exits</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of random exits belonging to other rooms which point to this room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Other room #', 'text',
            'Region', 'text',
            'Exit #', 'text',
            'Direction', 'text',
            'Map direction', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);     # Fixed height

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

        # Add a single button
        my $button = $self->addButton(
            $table,
            'Refresh list',
            'Refresh the list of incoming random exits',
            undef,
            9, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->exits4Tab_refreshList(
                $slWidget,
                scalar (@columnList / 2),
                'randomExitHash',
            );
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub exits7Tab {

        # Exits7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Checked directions
        $self->addLabel($table, '<b>Checked directions</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);     # Fixed height

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

        # Add a single button
        my $button = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('C_ontents', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Temporary contents
        $self->addLabel($table, '<b>Temporary contents</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>List of objects which are currently in a non-model room</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Object name', 'text',
            'Object', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);     # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton(
            $table,
            'Reset',
            'Reset the list of objects',
            undef,
            8, 10, 10, 11,
        );
        $button->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 $button2 = $self->addButton(
            $table,
            'Clear',
            'Empty the list of objects',
            undef,
            10, 12, 10, 11,
        );
        $button2->signal_connect('clicked' => sub {

            # Empty the list
            $self->ivAdd('editHash', 'tempObjList', []);

            # Refresh the simple list
            $self->contents1Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub contents1Tab_refreshList {

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

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

        # Local variables
        my (@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
        foreach my $obj (@objList) {

            push (@dataList,
                $obj->name,
                $obj,
            );
        }

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

        return 1;
    }

    sub contents2Tab {

        # Contents2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Hidden objects
        $self->addLabel($table, '<b>Hidden objects</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Add entry boxes
        $self->addLabel($table, 'Object #',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($table, 'Command to obtain it (optional)',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub contents3Tab {

        # Contents3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab($tabTitle, $innerNotebook);

        # Search responses
        $self->addLabel($table, '<b>Search responses</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Add entry boxes
        $self->addLabel($table, 'Thing\'s name',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($table, 'Response (optional)',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', undef, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('Pr_otocols', $self->notebook);

        # Add tabs to the inner notebook
        $self->protocols1Tab($innerNotebook);
        $self->protocols2Tab($innerNotebook);

        return 1;
    }

    sub protocols1Tab {

        # Protocols1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab($tabTitle, $innerNotebook);

        # Room data
        $self->addLabel($table, '<b>Room data</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);      # Fixed height

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub protocols2Tab {

        # Protocols2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab($tabTitle, $innerNotebook);

        # Exit data
        $self->addLabel($table, '<b>Exit data</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);      # Fixed height

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('C_haracter', $self->notebook);

        # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Minion', $self->notebook);

        # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Sentient', $self->notebook);

        # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('C_reature', $self->notebook);

        # 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 ($vBox, $table) = $self->addTab('Po_rtable', $self->notebook);

        # Properties belonging only to portable world model objects
        $self->addLabel($table, '<b>Properties belonging only to portable model objects</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Object\'s type (further classifies the type of portable object)',
            1, 8, 1, 2);
        @comboList = $self->session->currentDict->portableTypeList;
        $self->addComboBox($table, 'type', \@comboList, '',
            TRUE,               # No 'undef' value used
            8, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Decoration', $self->notebook);

        # Properties belonging only to decoration world model objects
        $self->addLabel($table, '<b>Properties belonging only to decoration model objects</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Object\'s type (further classifies the type of decoration object)',
            1, 8, 1, 2);
        @comboList = $self->session->currentDict->decorationTypeList;
        $self->addComboBox($table, 'type', \@comboList, '',
            TRUE,               # No 'undef' value used
            8, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
#       my ($vBox, $table) = $self->addTab('_Room', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 object 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Room', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Wilderness mode
        $self->addLabel($table, '<b>Wilderness mode</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'normal\'', 'wildMode',
            'normal',          # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel(
            $table,
            "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(
            $table,
            "<i>In \'update\' mode, the automapper window draws new exits</i>",
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'wild\'', 'wildMode', 'wild', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $table,
            "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(
            $table,
            '<i>In \'update\' mode, the automapper window doesn\'t draw new exits</i>',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'border\'', 'wildMode', 'border', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $table,
            "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(
            $table,
            "<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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub ownRoom2Tab {

        # OwnRoom2Tab tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Room titles
        $self->addLabel($table, '<b>Room titles</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>List of all known titles for this room</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'titleList', TRUE,
            1, 12, 4, 6);

        # Room guild
        $self->addLabel($table, '<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($table, 'roomGuild', \@comboList, 'Choose a guild:',
            FALSE,              # 'undef' value allowed
            1, 6, 8, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Contents', $self->notebook);

        # 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 ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Profile name
        $self->addLabel($table, '<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($table, '',
            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($table, 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($table, '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($table);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 object 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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Profile category
        $self->addLabel($table, '<b>Profile category</b>',
            0, 6, 3, 4);
        $self->addEntry($table, 'category', FALSE,
            1, 6, 4, 5, 16, 16);

        # Parent world profile
        $self->addLabel($table, '<b>Parent world profile</b>',
            7, 13, 0, 1);
        $self->addEntry($table, 'parentWorld', FALSE,
            8, 13, 1, 2);

        # Current profile
        $self->addLabel($table, '<b>Current profile</b>',
            7, 13, 3, 4);

        my $checkbutton = Gtk2::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);
        $table->attach_defaults($checkbutton, 7, 13, 4, 5);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Commands', $self->notebook);

        # List of profile-specific commands
        $self->addLabel($table, '<b>List of profile-specific commands</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>(Character-independent)</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'cmdList', TRUE,
            1, 12, 4, 6,
            undef, undef, undef, undef,
            -1, 300);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Fight', $self->notebook);

        # List of profile-specific fight patterns
        $self->addLabel($table, '<b>List of profile-specific fight patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen during a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 270);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, 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(
            $table,
            $slWidget,
            'fightMsgList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef);

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('Int_eraction', $self->notebook);

        # List of profile-specific interaction patterns
        $self->addLabel($table, '<b>List of profile-specific interaction patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen during an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 270);       # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'interactionMsgList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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,
                    $backRef,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionMsgList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Initial', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Initial tasks
        $self->addLabel($table, '<b>Initial tasks</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);     # Fixed height

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

        # Add editing buttons and combos
        my $combo = $self->addComboBox($table, undef, [], '',
            TRUE,           # No 'undef' value used
            1, 4, 10, 11);
        $self->initial1Tab_refreshCombo($combo);

        my $button = $self->addButton(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub initial1Tab_refreshList {

        # Called by $self->initial1Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Initial tasks
        $self->addLabel($table, '<b>Initial scripts</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of initial scripts that start when this profile becomes current</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Script' => 'text',
            'Run mode' => 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 13, 2, 8,
            -1, 200);     # Fixed height

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

        # Add editing entry boxes/buttons
        $self->addLabel($table, 'Script name',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 7, 8, 9);

        $self->addLabel($table, '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($table, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            9, 13, 8, 9);

        my $button = $self->addButton(
            $table,
            '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(
            $table,
            'Edit',
            'Open the selected script in a text editor',
            undef,
            3, 5, 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(
            $table,
            'Delete',
            'Delete the selected script',
            undef,
            9, 11, 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($table,
            'Move up', 'Move the selected script up the list', undef,
            5, 7, 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($table,
            'Move down', 'Move the selected script down the list', undef,
            7, 9, 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(
            $table,
            'Refresh list',
            'Refresh the list of initial scripts',
            undef,
            11, 13, 9, 10);
        $button6->signal_connect('clicked' => sub {

            # Reset the simple list
            $self->initial2Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        my $button7 = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub initial2Tab_refreshList {

        # Called by $self->initial2Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # 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($table, '<b>Initial mission</b>',
            0, 11, 0, 1);
        $self->addLabel($table,
            '<i>The mission to start when a session begins with this as a current profile</i>',
            1, 11, 1, 2);
        $self->addLabel($table,
            '<i>(N.B. Current profiles are checked in priority order; only the first mission'
            . ' found is started)</i>',
            1, 11, 2, 3);
        $self->addEntryWithButton($table, 'initMission', TRUE,
            1, 12, 3, 4);

        # Initial command list
        $self->addLabel($table, '<b>Initial command list</b>',
            0, 11, 4, 5);
        $self->addLabel($table,
            '<i>List of commands executed when a session begins with this as a current'
            . ' profile</i>',
            1, 11, 5, 6);
        $self->addLabel($table,
            '<i>(N.B. Current profiles are checked in priority order; commands are sent from all'
            . ' profiles</i>',
            1, 11, 6, 7);
        $self->addLabel($table,
            '<i>but no duplicate commands are sent)</i>',
            2, 11, 7, 8);
        $self->addTextView($table, 'initCmdList', TRUE,
            1, 12, 8, 12,
            TRUE, TRUE, FALSE, FALSE,
            -1, 160);           # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        my ($vBox, $table) = $self->addTab('N_otes', $self->notebook);

        # Notes
        $self->addLabel($table, '<b>Notes</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Persistent notes for this profile (also used by the Notepad task)</i>',
            1, 12, 1, 2);

        my $textView = $self->addTextView($table, 'notepadList', TRUE,
            1, 12, 2, 11,
            TRUE, FALSE, FALSE, FALSE,     # Treat as a list, don't remove empty lines or whitespace
            -1, 300);                      # Fixed height
        my $button = $self->addButton($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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();
                }
            }

            # 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 object 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
        #   $table  -   The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Profile category
        $self->addLabel($table, '<b>Profile category</b>',
            0, 6, 3, 4);
        $self->addEntry($table, 'category', FALSE,
            1, 6, 4, 5, 16, 16);

        # World long name
        $self->addLabel($table, '<b>World long name</b>',
            7, 13, 0, 1);
        $self->addEntryWithIcon($table, 'longName', 'string', 1, undef,
            8, 13, 1, 2);

        $self->addLabel($table, '<i>No length or character limits</i>',
            8, 13, 2, 3);

        # Current profile
        $self->addLabel($table, '<b>Current profile</b>',
            7, 13, 3, 4);

        my $checkbutton = Gtk2::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);
        $table->attach_defaults($checkbutton, 7, 13, 4, 5);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('Settin_gs', $self->notebook);

        # 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 Gtk2::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 . '->settings1Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Connection details
        $self->addLabel($table, '<b>Connection details</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'DNS',
            1, 3, 1, 2);
        $self->addEntryWithIcon($table, 'dns', 'string', undef, undef,
            3, 6, 1, 2);
        $self->addLabel($table, 'IPV4',
            1, 3, 2, 3);
        $self->addEntryWithIcon($table, 'ipv4', \&ipv4Check, undef, undef,
            3, 6, 2, 3);
        $self->addLabel($table, 'IPV6',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'ipv6', \&ipv6Check, undef, undef,
            3, 6, 3, 4);
        $self->addLabel($table, 'Port',
            1, 3, 4, 5);
        $self->addEntryWithIcon($table, 'port', 'int', 0, 65535,
            3, 6, 4, 5);
        $self->addLabel($table, 'Protocol',
            1, 3, 5, 6);
        $self->addComboBox($table, 'protocol', ['telnet', 'ssh', 'ssl'], '',
            TRUE,           # No 'undef' value used
            3, 6, 5, 6);
        # SSH Connection details
        $self->addLabel($table, '<b>SSH Connection details</b>',
            0, 6, 6, 7);
        $self->addLabel($table, 'SSH username',
            1, 3, 7, 8);
        $self->addEntryWithIcon($table, 'sshUserName', 'string', undef, undef,
            3, 6, 7, 8);
        $self->addLabel($table, 'SSH password',
            1, 3, 8, 9);
        $self->addEntryWithIcon($table, 'sshPassword', 'string', undef, undef,
            3, 6, 8, 9);
        $self->addLabel($table, 'Specify port in SSH connections',
            1, 5, 9, 10);
        $self->addCheckButton($table, 'sshPortFlag', TRUE,
            5, 6, 9, 10, 1, 0.5);

        # Previous connections
        $self->addLabel($table, '<b>Previous connections</b>',
            7, 12, 0, 1);
        $self->addLabel($table, 'Last connect date',
            7, 9, 1, 2);
        $self->addEntry($table, 'lastConnectDate', FALSE,
            9, 12, 1, 2, 20, 20);        # Max chars
        $self->addLabel($table, 'Last connect time',
            7, 9, 2, 3);
        $self->addEntry($table, 'lastConnectTime', FALSE,
            9, 12, 2, 3, 20, 20);        # Max chars
        $self->addLabel($table, 'Last character used',
            7, 9, 3, 4);
        $self->addEntry($table, 'lastConnectChar', FALSE,
            9, 12, 3, 4, 20, 20);        # Max chars
        $self->addLabel($table, 'Total no. connections',
            7, 9, 4, 5);
        $self->addEntry($table, 'numberConnects', FALSE,
            9, 12, 4, 5, 20, 20);        # Max chars

        # General settings
        $self->addLabel($table, '<b>General settings</b>',
            7, 12, 5, 6);
        $self->addLabel($table, 'Ever a current profile?',
            7, 11, 6, 7);
        $self->addCheckButton($table, 'setupCompleteFlag', FALSE,
            11, 12, 6, 7, 1, 0.5);
        $self->addLabel($table, 'World can\'t be saved (during this session)',
            7, 11, 7, 8);
        $self->addCheckButton($table, 'noSaveFlag', FALSE,
            11, 12, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Life status override',
            7, 11, 8, 9);
        $self->addCheckButton($table, 'lifeStatusOverrideFlag', TRUE,
            11, 12, 8, 9);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Connection history
        $self->addLabel($table, '<b>Connection history</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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($table, undef, \@columnList,
            1, 12, 2, 10, -1, 270);

        # Initialise the list, which starts in reverse order
        $reverseFlag = TRUE;
        $self->settings2Tab_refreshList($slWidget, (scalar @columnList / 2), $reverseFlag);

        # Add editing buttons
        $self->addLabel($table, '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($table, undef, \@comboList, '', TRUE,
            2, 6, 10, 11);

        my $button = $self->addButton($table,
            '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($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings2Tab_refreshList {

        # Resets the simple list displayed by $self->settings2Tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Websites. ->signal_connects appear further below
        $self->addLabel($table, '<b>Websites</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Website URL',
            1, 3, 1, 2);
        my $entry = $self->addEntry($table, 'worldURL', TRUE,
            3, 10, 1, 2);
        my $button = $self->addButton($table,
            'View', 'View this link', undef,
            10, 12, 1, 2);

        $self->addLabel($table, 'Referrer URL',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($table, 'referURL', TRUE,
            3, 10, 2, 3);
        my $button2 = $self->addButton($table,
            '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);
            }
        });

        $self->addLabel($table, '<b>World description</b>',
            0, 3, 3, 4);
        $self->addLabel($table, 'General description',
            1, 3, 4, 5);
        $self->addLabel($table, '',         # Empty labels to get the spacing right
            1, 3, 5, 6);
        $self->addLabel($table, '',
            1, 3, 6, 7);
        $self->addLabel($table, '',
            1, 3, 7, 8);
        $self->addLabel($table, '',
            1, 3, 8, 9);
        $self->addLabel($table, '',
            1, 3, 9, 10);
        $self->addTextView($table, 'worldDescrip', TRUE,
            3, 12, 4, 10,
            FALSE, undef, undef, TRUE,      # Treat as a scalar, not a list/forbid horizontal scroll
            -1, 200);                       # Fixed height

        $self->addLabel($table, 'World hint',
            1, 3, 10, 11);
        $self->addEntry($table, 'worldHint', TRUE,
            3, 12, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings4Tab {

        # Settings4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Automatic login mode
        $self->addLabel($table, '<b>Automatic login mode</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'none\'', 'loginMode',
            'none',          # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 4, 1, 2);
        $self->addLabel($table, 'No automatic login (user must use \';login\' command)',
            4, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'immediate\'', 'loginMode', 'immediate', TRUE,
            1, 4, 2, 3);
        $self->addLabel($table, 'Immediate login (not recommended)',
            4, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'lp\'', 'loginMode', 'lp', TRUE,
            1, 4, 3, 4);
        $self->addLabel(
            $table, 'LP/Diku/AberMUD login (consecutive prompts for character/password)',
            4, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'tiny\'', 'loginMode', 'tiny', TRUE,
            1, 4, 4, 5);
        $self->addLabel(
            $table, 'TinyMUD login (send \'connect char pass\' at first prompt)',
            4, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'world_cmd\'', 'loginMode', 'world_cmd', TRUE,
            1, 4, 5, 6);
        $self->addLabel(
            $table, 'Send a sequence of world commands at the first prompt (see next tab)',
            4, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'telnet\'', 'loginMode', 'telnet', TRUE,
            1, 4, 6, 7);
        $self->addLabel(
            $table, 'Basic telnet login (e.g. \'login:\', \'password:\')',
            4, 12, 6, 7);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'task\'', 'loginMode', 'task', TRUE,
            1, 4, 7, 8);
        $self->addLabel(
            $table, 'Run a task to handle the login (see next tab)',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'script_task\'', 'loginMode', 'script_task', TRUE,
            1, 4, 8, 9);
        $self->addLabel(
            $table,
            'Run ' . $axmud::BASIC_ARTICLE . ' script from within a task to handle the login (see'
            . ' next tab)',
            4, 12, 8, 9);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'script\'', 'loginMode', 'script', TRUE,
            1, 4, 9, 10);
        $self->addLabel(
            $table, 'Run ' . $axmud::BASIC_ARTICLE . ' script to handle the login (see next tab)',
            4, 12, 9, 10);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'mission\'', 'loginMode', 'mission', TRUE,
            1, 4, 10, 11);
        $self->addLabel(
            $table, 'Start a mission to handle the login (see next tab)',
            4, 12, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings5Tab {

        # Settings5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Automatic login mode (2)
        $self->addLabel($table, '<b>Automatic login mode (2)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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(
            $table,
            '<i>(NB In mode \'world_cmd\', if world commands contain @name@, @account@ and/or'
            . ' @password@, they are substituted)</i>',
            1, 12, 2, 3);

        $self->addLabel($table, 'Mode \'tiny\': List of login expected patterns',
            1, 8, 3, 4);
        $self->addTextView($table, 'loginConnectPatternList', TRUE,
            1, 8, 4, 6,
            undef, undef, undef, undef,
            -1, 120);           # Fixed height

        $self->addLabel($table, 'Mode \'world_cmd\': List of world commands to send',
            8, 12, 3, 4);
        $self->addTextView($table, 'loginCmdList', TRUE,
            8, 12, 4, 6,
            undef, undef, undef, undef,
            -1, 120);           # Fixed height

        $self->addLabel(
            $table,
            'Mode \'lp\', \'tiny\', \'world_cmd\', \'telnet\': Login success patterns',
            1, 8, 7, 8);
        $self->addTextView($table, 'loginSuccessPatternList', TRUE,
            1, 8, 8, 12,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
            -1, 120);           # Fixed height

        $self->addLabel($table, 'Task/script/mission modes: task/script/mission to start',
            8, 12, 7, 8);
        my $entry = $self->addEntry($table, 'loginObjName', TRUE,
            8, 12, 8, 9);

        # Add a button
        my $button = $self->addButton($table,
            '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($table, '',     # Get table spacing right using empty labels
            8, 12, 10, 11);
        $self->addLabel($table, '',     # Get table spacing right using empty labels
            8, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings6Tab {

        # Settings6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Automatic login mode (3)
        $self->addLabel($table, '<b>Automatic login mode (3)</b>',
            0, 12, 0, 1);

        $self->addLabel($table,
            'Mode \'mission\': List of patterns matching lines with character names, and'
            . ' world command substitutions',
            1, 12, 1, 2);
        $self->addLabel($table,
            '    <i>(e.g. line  <b>1 Gandalf</b>  -  pattern'
            . ' <b>^(\d+)\s(\w+)$</b>  -  character backref <b>2</b>  -  command <b> "$1"</b>'
            . '  )</i>',
            1, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref', 'int',
            'Substitution', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 3, 9,
            -1, 180);       # Fixed height

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

        # Add entries for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        $self->addLabel($table, 'Backref:',
            1, 3, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($table, 'World command:',
            6, 8, 10, 11);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            8, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'loginSpecialList',
            11, 3,
            $entry, $entry2, $entry3,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef, $cmd);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef, $cmd,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'loginSpecialList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings7Tab {

        # Settings7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Automatic login mode
        $self->addLabel($table, '<b>Automatic quit mode</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>What happens when you use the client commands \';quit\' , \';qquit\' or'
            . ' \';quitall\'</i>',
            1, 12, 1, 2);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'normal\'', 'autoQuitMode',
            'normal',   # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 2, 3);
        $self->addLabel(
            $table,
            'Send standard \'quit\' world command',
            3, 6, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'world_cmd\'', 'autoQuitMode', 'world_cmd', TRUE,
            7, 9, 2, 3);
        $self->addLabel($table, 'Send a sequence of world commands',
            9, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'task\'', 'autoQuitMode', 'task', TRUE,
            1, 3, 3, 4);
        $self->addLabel(
            $table, 'Run a task',
            3, 6, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'task_script\'', 'autoQuitMode', 'task_script', TRUE,
            7, 9, 3, 4);
        $self->addLabel(
            $table, 'Run ' . $axmud::BASIC_ARTICLE . ' script from within a task',
            9, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'script\'', 'autoQuitMode', 'script', TRUE,
            1, 3, 4, 5);
        $self->addLabel(
            $table, 'Run an ' . $axmud::BASIC_ARTICLE . ' script',
            3, 6, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'mission\'', 'autoQuitMode', 'mission', TRUE,
            7, 9, 4, 5);
        $self->addLabel(
            $table, 'Start a mission',
            9, 12, 4, 5);

        $self->addLabel($table, 'Mode \'world_cmd\': List of world commands to send',
            1, 6, 5, 6);
        $self->addTextView($table, 'autoQuitCmdList', TRUE,
            1, 6, 6, 12,
            undef, undef, undef, undef,
            -1, 200);           # Fixed height

        $self->addLabel($table, 'Other modes: task, script or mission to start',
            7, 12, 5, 6);
        my $entry = $self->addEntry($table, 'autoQuitObjName', TRUE,
            7, 12, 6, 7);

        # Add a button
        my $button = $self->addButton($table,
            '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);
                }
            }
        });

        $self->addLabel($table, '',     # Get table spacing right using empty labels
            7, 12, 8, 9);
        $self->addLabel($table, '',
            7, 12, 9, 10);
        $self->addLabel($table, '',
            7, 12, 10, 11);
        $self->addLabel($table, '',
            7, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings8Tab {

        # Settings8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _8', $innerNotebook);

        # Character/password list
        $self->addLabel($table, '<b>Character/password list</b>',
            0, 6, 0, 1);

        $self->addLabel($table,
            '<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($table, undef, TRUE,
            1, 2, 2, 3);
        # Passwords are initially obscured
        $obscureFlag = TRUE;
        $checkButton->set_active($obscureFlag);
        # (Signal connect follows)

        $self->addLabel($table, 'Obscure passwords',
            2, 6, 2, 3);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'unknown\'', 'loginAccountMode',
            'unknown',  # IV set to this value when toggled
            TRUE,       # Sensitive widget
            6, 7, 2, 3);
        $self->addLabel($table, 'Unknown if account name is also required to log in',
            7, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'not_required\'', 'loginAccountMode', 'not_required', TRUE,
            6, 7, 3, 4);
        $self->addLabel($table, 'Account name not required',
            7, 12, 3, 4);

         ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'required\'', 'loginAccountMode', 'required', TRUE,
            6, 7, 4, 5);
        $self->addLabel($table, 'Account name required',
            7, 12, 4, 5);

        # Add a simple list
        @columnList = (
            'Character', 'text',
            'Password', 'text',
            'Associated account name (if any)', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 5, 11, -1, 190);      # Fixed height

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

        # signal_connect for earlier checkbutton
        $checkButton->signal_connect('toggled' => sub {

            $obscureFlag = $checkButton->get_active();
            $self->settings8Tab_refreshList($slWidget, (scalar @columnList / 2), $obscureFlag);
        });

        # Add editing buttons
        my $button = $self->addButton($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings8Tab_refreshList {

        # Resets the simple list displayed by $self->settings8Tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _9', $innerNotebook);

        # Missions list
        $self->addLabel($table, '<b>Associated profiles</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10, -1, 270);      # Fixed height

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

        # Add a button
        my $button = $self->addButton($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings10Tab {

        # Settings10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 1_0', $innerNotebook);

        # Dictionary settings
        $self->addLabel($table, '<b>Dictionary settings</b>',
            0, 6, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@comboList, '',
            $noUndefFlag,
            1, 4, 2, 3);
        $self->settings10Tab_refreshCombo($combo);

        my $button = $self->addButton($table, '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($table, 'New...', 'Add new dictionary', undef,
            1, 3, 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(
            $table, 'Refresh list', 'Refresh the list of dictionaries', undef,
            3, 6, 3, 4);
        $button3->signal_connect('clicked' => sub {

            # Refresh the combo
            $self->settings10Tab_refreshCombo($combo);
        });

        # Wimpy settings
        $self->addLabel($table, '<b>Wimpy settings</b>',
            0, 6, 4, 5);
        $self->addLabel($table, 'World wimpy maximum',
            1, 4, 5, 6);
        $self->addEntryWithIcon($table, 'remoteWimpyMax', 'int', 0, undef,
            4, 6, 5, 6, 8, 8);
        $self->addLabel($table, $axmud::SCRIPT . ' wimpy maximum',
            1, 4, 6, 7);
        $self->addEntry($table, 'constLocalWimpyMax', FALSE,
            4, 6, 6, 7, 8, 8);

        # Multiples
        $self->addLabel($table, '<b>Multiples</b>',
            0, 12, 7, 8);
        $self->addLabel($table, 'Add numbers to similar objects',
            1, 5, 8, 9);
        $self->addCheckButton($table, 'numberedObjFlag', TRUE,
            5, 6, 8, 9);
        $self->addLabel($table, 'Multiple object pattern',
            1, 4, 9, 10);
        $self->addEntry($table, 'multiplePattern', TRUE,
            4, 6, 9, 10);

        # Consecutive empty line suppression
        $self->addLabel($table, '<b>Consecutive empty line suppression</b>',
            7, 13, 0, 1);
        $self->addLabel($table, 'Lines to suppress (0 - none, 1 - all)',
            8, 11, 1, 2);
        $self->addEntryWithIcon($table, 'suppressEmptyLineCount', 'int', 0, undef,
            11, 13, 1, 2, 8, 8);
        $self->addLabel($table, 'Suppress empty lines before login',
            8, 12, 2, 3);
        $self->addCheckButton($table, 'suppressBeforeLoginFlag', TRUE,
            12, 13, 2, 3);

        # Command separator
        $self->addLabel($table, '<b>Command separator \'' . $axmud::CLIENT->cmdSep . '\'</b>',
            7, 12, 3, 4);
        $self->addLabel($table, 'Auto-separate commands in this world',
            8, 12, 4, 5);
        $self->addCheckButton($table, 'autoSeparateCmdFlag', TRUE,
            12, 13, 4, 5);

        # Slowwalking
        $self->addLabel($table, '<b>Slowwalking</b>',
            7, 13, 5, 6);
        $self->addLabel($table, 'Max world commands (0 - no limit)',
            8, 11, 6, 7);
        $self->addEntryWithIcon($table, 'excessCmdLimit', 'int', 0, undef,
            11, 13, 6, 7, 6, 4);
        $self->addLabel($table, '...in time period (min 0.1 secs)',
            8, 11, 7, 8);
        $self->addEntryWithIcon($table, 'excessCmdDelay', 'int', 0.1, undef,
            11, 13, 7, 8, 6, 4);


        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings10Tab_refreshCombo {

        # Resets the combobox displayed by $self->settings10Tab
        #
        # Expected arguments
        #   $combo          - The Gtk2::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 Gtk2::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 ($vBox, $table) = $self->addTab('Page 11', $innerNotebook);

        # Units
        $self->addLabel($table, '<b>Units</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Character age units',
            1, 6, 1, 2);
        $self->addEntryWithButton($table, 'charAgeUnit', TRUE,
            1, 6, 2, 3);
        $self->addLabel($table, 'Standard weight unit',
            1, 6, 3, 4);
        $self->addEntryWithButton($table, 'weightUnit', TRUE,
            1, 6, 4, 5);
        $self->addLabel($table, 'Strings for weight units (inc. plurals)',
            7, 12, 1, 2);
        $self->addTextView($table, 'weightUnitStringList', TRUE,
            7, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
            -1, 100);                 # Fixed width/height

        # Command prompt patterns
        $self->addLabel($table, '<b>Command prompt patterns (matches whole prompt)</b>',
            0, 12, 6, 7);
        $self->addLabel($table, 'Enable strict prompts mode',
            7, 11, 6, 7);
        $self->addCheckButton($table, 'strictPromptsMode', TRUE,
            11, 12, 6, 7);
        $self->addTextView($table, 'cmdPromptPatternList', TRUE,
            1, 13, 7, 12,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
            -1, 180);                 # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings12Tab {

        # Settings12 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 12', $innerNotebook);

        # Session logging settings
        $self->addLabel($table, '<b>Session logging settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of logfiles written by each session using this world profile</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Logfile', 'text',
            'Write?', 'bool',
            'Description', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);      # Fixed height

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

        # Add a two combos and a button
        @comboList = $axmud::CLIENT->constSessionLogOrderList;
        my $comboBox = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            1, 4, 10, 11);

        my $button = $self->addButton($table,
            'Toggle', 'Change the setting for the selected logfile', undef,
            4, 6, 10, 11,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

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

            # Update the IV
            $self->session->pseudoCmd('log ' . $logfile, $self->pseudoCmdMode);

            # Refresh the simple list
            $self->settings12Tab_refreshList($slWidget, (scalar @columnList / 2));
        });

        # Widgets can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings12Tab_refreshList {

        # Resets the simple list displayed by $self->settings12Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Override', $self->notebook);

        # 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 Gtk2::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 . '->override1Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Left column
        $self->addLabel($table, '<b>Override ' . $axmud::SCRIPT . ' terminal type</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Own termtype',
            1, 3, 1, 2);
        @comboList = $axmud::CLIENT->constTermTypeList;
        $self->addComboBox($table, 'termType', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 1, 2);

        $self->addLabel($table, '<b>Override ' . $axmud::SCRIPT . ' character set</b>',
            0, 6, 2, 3);
        $self->addLabel($table, 'Own charset',
            1, 3, 3, 4);
        @comboList2 = $axmud::CLIENT->charSetList;
        $self->addComboBox($table, 'worldCharSet', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            3, 6, 3, 4);

        # Right column
        $self->addLabel($table, '<b>Override real terminal size (disables NAWS)</b>',
            7, 13, 0, 1);
        $self->addLabel($table, 'Columns',
            8, 10, 1, 2);
        $self->addEntryWithIcon($table, 'columns', 'int', 0, undef,
            10, 13, 1, 2);
        $self->addLabel($table, 'Rows',
            8, 10, 2, 3);
        $self->addEntryWithIcon($table, 'rows', 'int', 0, undef,
            10, 13, 2, 3);
        $self->addLabel($table, 'Send size after login',
            8, 12, 3, 4);
        $self->addCheckButton($table, 'sendSizeInfoFlag', TRUE,
            12, 13, 3, 4, 1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub override2Tab {

        # Override2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Telnet override settings
        $self->addLabel($table, '<b>Telnet override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 10,
            -1, 230);       # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Option/protocol:',
            1, 3, 10, 11);

        @comboList = qw(
            echo sga ttype eor naws new_environ charset
            msdp mssp mccp msp mxp pueblo zmp aard102 atcp gmcp mtts mcp
        );

        my $comboBox = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 5, 10, 11);

        my $button = $self->addButton(
            $table,
            'Disable for this world',
            'Stop this option/protocol from being enabled in this world\'s session',
            undef,
            5, 8, 10, 11);
        $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(
            $table,
            'Enable/disable according to global settings',
            'Enable/disable this option/protocol according to global settings',
            undef,
            8, 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(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub override2Tab_refreshList {

        # Resets the simple list displayed by $self->override2Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # MXP override settings
        $self->addLabel($table, '<b>MXP override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 10,
            -1, 230);       # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Setting type:',
            1, 3, 10, 11);

        @setupList = (
            'room'      => 'Use MXP room data',
        );

        do {

            my ($type, $descrip);

            $type = shift @setupList;
            $descrip = shift @setupList;

            push (@comboList, $descrip);
            $comboHash{$descrip} = $type;

        } until (! @setupList);

        my $comboBox = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 5, 10, 11);

        my $button = $self->addButton(
            $table,
            'Enable for this world',
            'Enable this setting in this world\'s sessions',
            undef,
            5, 7, 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(
            $table,
            'Disable for this world',
            'Disable this setting in this world\'s sessions',
            undef,
            7, 9, 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(
            $table,
            'Use global settings',
            'Enable/disable this setting according to global settings',
            undef,
            9, 12, 10, 11);
        $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(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub override3Tab_refreshList {

        # Resets the simple list displayed by $self->override3Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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') {

            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 Gtk2::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 . '->override4Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Terminal type override settings
        $self->addLabel($table, '<b>Terminal type override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 3, 8,
            -1, 160);       # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'TTYPE negotiations',
            1, 3, 8, 9);

        @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($table, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 8, 8, 9);

        my $button = $self->addButton($table, 'Use', 'Use this setting in the session', undef,
            8, 10, 8, 9);
        $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(
            $table,
            'Don\'t use',
            'Don\'t use this setting in the session',
            undef,
            10, 12, 8, 9);
        $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($table, 'Custom client name',
            1, 3, 9, 10);
        my $entry = $self->addEntry($table, undef, TRUE,
            3, 8, 9, 10);

        my $button3 = $self->addButton($table, 'Use', 'Use this setting in the session', undef,
            8, 10, 9, 10);
        $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(
            $table,
            'Don\'t use',
            'Don\'t use this setting in the session',
            undef,
            10, 12, 9, 10);
        $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($table, 'Custom client version',
            1, 3, 10, 11);
        my $entry2 = $self->addEntry($table, undef, TRUE,
            3, 8, 10, 11);

        my $button5 = $self->addButton($table, 'Use', 'Use this setting in the session', undef,
            8, 10, 10, 11);
        $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(
            $table,
            'Don\'t use',
            'Don\'t use this setting in the session',
            undef,
            10, 12, 10, 11);
        $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');
        });

        my $button7 = $self->addButton(
            $table,
            'Reset',
            'Reset the list of override settings',
            undef,
            8, 10, 11, 12);
        $button7->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('');
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });

        my $button8 = $self->addButton(
            $table,
            'Clear',
            'Clear the list of override settings',
            undef,
            10, 12, 11, 12);
        $button8->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('');
            # Refresh the simple list
            $self->refreshList_hashIV($slWidget, (scalar @columnList / 2), 'termOverrideHash');
        });


        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub override5Tab {

        # Override5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Instruction override settings
        $self->addLabel($table, '<b>Instruction sigil override settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 10,
            -1, 230);       # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, '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($table, undef, \@comboList, '',
            TRUE,                       # No 'undef' value used
            3, 5, 10, 11);

        my $button = $self->addButton(
            $table,
            'Enable for this world',
            'Enable the sigil in this world\'s sessions',
            undef,
            5, 7, 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(
            $table,
            'Disable for this world',
            'Disable the sigil in this world\'s sessions',
            undef,
            7, 9, 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(
            $table,
            'Use global settings',
            'Enable/disable this sigil according to global settings',
            undef,
            9, 12, 10, 11);
        $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(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub override5Tab_refreshList {

        # Resets the simple list displayed by $self->override5Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Rooms', $self->notebook);

        # 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);

        return 1;
    }

    sub rooms1Tab {

        # Rooms1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Room statement components
        $self->addLabel($table, '<b>Room statement components</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new components
        $self->addLabel($table, '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($table, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            2, 6, 8, 9);
        # ->signal_connect appears below...

        $self->addLabel($table, 'Name:',
            7, 8, 8, 9);
        my $entry = $self->addEntryWithIcon($table, 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($table, '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($table, '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(
            $table,
            '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($table, '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($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms1Tab_refreshList {

        # Resets the simple list displayed by $self->rooms1Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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
        @sortedList = sort {lc($a) cmp lc($b)} (@comboList);

        # 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 Gtk2::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 ($vBox, $table);

        if ($iv eq 'verboseComponentList') {

            ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

            # Verbose components
            $self->addLabel($table, '<b>Verbose components</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<i>List of components found in verbose room statements</i>',
                1, 12, 1, 2);

        } elsif ($iv eq 'shortComponentList') {

            ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

            # Short verbose components
            $self->addLabel($table, '<b>Short verbose components</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<i>List of components found in short verbose room statements</i>',
                1, 12, 1, 2);

        } else {

            ($vBox, $table) = $self->addTab('Page _8', $innerNotebook);

            # Short verbose components
            $self->addLabel($table, '<b>Brief components</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);     # Fixed height

        # 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($table, 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(
            $table, '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($table, '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(
            $table,
            '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($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms2Tab_refreshList {

        # Resets the simple list displayed by $self->rooms2Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table);

        if ($listIV eq 'verboseAnchorPatternList') {

            ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

            # Verbose components
            $self->addLabel($table, '<b>Verbose anchor patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<i>List of patterns which mark a line as a verbose anchor line</i>',
                1, 12, 1, 2);

        } elsif ($listIV eq 'shortAnchorPatternList') {

            ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

            # Short verbose components
            $self->addLabel($table, '<b>Short verbose anchor patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<i>List of patterns which mark a line as a short verbose anchor line</i>',
                1, 12, 1, 2);

        } elsif ($listIV eq 'briefAnchorPatternList') {

            ($vBox, $table) = $self->addTab('Page _9', $innerNotebook);

            # Short verbose components
            $self->addLabel($table, '<b>Brief anchor patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<i>List of patterns which mark a line as a brief anchor line</i>',
                1, 12, 1, 2);
        }

        $self->addTextView($table, $listIV, TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE, # Treat as a list, remove empty lines, don't remove whitespace
            -1, 200);                 # Fixed height

        $self->addLabel($table,
            '<i>Anchor line\'s relationship with other components in the component list</i>',
            1, 12, 6, 7);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, 'Mode -1', $offsetIV,
            -1,         # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 4, 7, 8);
        $self->addLabel($table, 'Anchor line shares a line with the component before it',
            4, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, 'Mode 0', $offsetIV, 0, TRUE,
            1, 4, 8, 9);
        $self->addLabel($table, 'Anchor line does not share its line with any other component',
            4, 12, 8, 9);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, 'Mode +1', $offsetIV, 1, TRUE,
            1, 4, 9, 10);
        $self->addLabel($table, 'Anchor line shares a line with the component after it',
            4, 12, 9, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms4Tab {

        # Rooms4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table);

        if ($iv eq 'verboseAnchorCheckList') {

            ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

            # Verbose components
            $self->addLabel($table, '<b>Verbose check patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<i>List of patterns found near a verbose anchor line</i>',
                1, 12, 1, 2);

        } elsif ($iv eq 'shortAnchorCheckList') {

            ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

            # Short verbose components
            $self->addLabel($table, '<b>Short check patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<i>List of patterns found near a short verbose anchor line</i>',
                1, 12, 1, 2);

        } else {

            ($vBox, $table) = $self->addTab('Page 1_0', $innerNotebook);

            # Short verbose components
            $self->addLabel($table, '<b>Brief check patterns</b>',
                0, 12, 0, 1);
            $self->addLabel($table,
                '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);     # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Line offset:',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'int', -16, 16,
            3, 6, 10, 11);
        $self->addLabel($table, 'Pattern:',
            7, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms11Tab {

        # Rooms11 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->rooms11Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page 11', $innerNotebook);

        # Other room statement markers
        $self->addLabel($table, '<b>Other room statement markers</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, 'OFF', 'basicMappingFlag',
            FALSE,         # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 1, 2);
        $self->addLabel($table, 'Basic mapping mode not required <i>(suitable for most worlds)</i>',
            3, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, 'ON', 'basicMappingFlag', TRUE, TRUE,
            1, 3, 2, 3);
        $self->addLabel($table,
            'Use basic mapping <i>(room statements don\'t contain matchable text: usually, no list'
            . ' of exits)</i>',
            3, 12, 2, 3);

        # (Not anchor patterns)
        $self->addLabel($table,
            '<i>Patterns which mean a line is definitely not an anchor line</i>',
            1, 12, 3, 4);

        $self->addTextView($table, 'notAnchorPatternList', TRUE,
            1, 12, 4, 8,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                  # Fixed height

        # (Room statement termination patterns)
        $self->addLabel(
            $table,
            '<i>Patterns which mean the whole room statement has been received (usually not'
            . ' necessary)</i>',
            1, 12, 8, 9);

        $self->addLabel($table, 'Verbose statement terminator',
            1, 4, 9, 10);
        $self->addEntry($table, 'verboseFinalPattern', TRUE,
            4, 12, 9, 10);
        $self->addLabel($table, 'Short verbose statement terminator',
            1, 4, 10, 11);
        $self->addEntry($table, 'shortFinalPattern', TRUE,
            4, 12, 10, 11);
        $self->addLabel($table, 'Brief statement terminator',
            1, 4, 11, 12);
        $self->addEntry($table, 'briefFinalPattern', TRUE,
            4, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms12Tab {

        # Rooms12 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 12', $innerNotebook);

        # Verbose exit delimiter/marker strings
        $self->addLabel($table,
            '<b>Verbose exit delimiter/marker strings</b>',
            0, 12, 0, 1);

        $self->addCheckButton($table, 'verboseExitSplitCharFlag', TRUE,
            6, 7, 0, 1);
        $self->addLabel(
            $table,
            'Split exits into single characters',
            7, 12, 0, 1);

        $self->addLabel($table, '<i>Delimiters (<b>not</b> regular expressions)</i>',
            1, 6, 1, 2);
        $self->addTextView($table, 'verboseExitDelimiterList', TRUE,
            1, 6, 2, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace
        $self->addLabel($table, '<i>Non-delimiters (<b>not</b> regular expressions)</i>',
            7, 12, 1, 2);
        $self->addTextView($table, 'verboseExitNonDelimiterList', TRUE,
            7, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        $self->addLabel($table, '<i>Left-side marker patterns</i>',
            1, 6, 6, 7);
        $self->addTextView($table, 'verboseExitLeftMarkerList', TRUE,
            1, 6, 7, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace
        $self->addLabel($table, '<i>Right-side marker patterns</i>',
            7, 12, 6, 7);
        $self->addTextView($table, 'verboseExitRightMarkerList', TRUE,
            7, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms13Tab {

        # Rooms13 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 13', $innerNotebook);

        # Brief exit delimiter/marker strings
        $self->addLabel($table,
            '<b>Brief exit delimiter/marker strings</b>',
            0, 12, 0, 1);

        $self->addCheckButton($table, 'briefExitSplitCharFlag', TRUE,
            6, 7, 0, 1);
        $self->addLabel(
            $table,
            'Split exits into single characters',
            7, 12, 0, 1);

        $self->addLabel($table, '<i>Delimiters (<b>not</b> regular expressions)</i>',
            1, 6, 1, 2);
        $self->addTextView($table, 'briefExitDelimiterList', TRUE,
            1, 6, 2, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace
        $self->addLabel($table, '<i>Non-delimiters (<b>not</b> regular expressions)</i>',
            7, 12, 1, 2);
        $self->addTextView($table, 'briefExitNonDelimiterList', TRUE,
            7, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        $self->addLabel($table, '<i>Left-side marker patterns</i>',
            1, 6, 6, 7);
        $self->addTextView($table, 'briefExitLeftMarkerList', TRUE,
            1, 6, 7, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace
        $self->addLabel($table, '<i>Right-side marker patterns</i>',
            7, 12, 6, 7);
        $self->addTextView($table, 'briefExitRightMarkerList', TRUE,
            7, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms14Tab {

        # Rooms14 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 14', $innerNotebook);

        # Exit state strings
        $self->addLabel($table, '<b>Exit state strings</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Strings (<b>not</b> 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($table, undef, \@columnList,
            1, 12, 2, 9,
            -1, 200);     # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Exit state',
            1, 3, 9, 10);
        @comboList = (
            'open',
            'closed',
            'locked',
            'secret',
            'secret_open',
            'secret_closed',
            'secret_locked',
            'impass',
            'dark',
            'danger',
            '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($table, undef, \@comboList, '',
            TRUE,           # No 'undef' value used
            3, 6, 9, 10);

        $self->addLabel($table, 'Start string',
            7, 9, 9, 10);
        my $entry = $self->addEntry($table, undef, TRUE,
            9, 12, 9, 10);

        $self->addLabel($table, 'Middle string',
            1, 3, 10, 11);
        my $entry2 = $self->addEntry($table, undef, TRUE,
            3, 6, 10, 11);

        $self->addLabel($table, 'End string',
            7, 9, 10, 11);
        my $entry3 = $self->addEntry($table, undef, TRUE,
            9, 11, 10, 11);

        my $button = $self->addButton(
            $table, 'Add', 'Add this exit state', undef,
            11, 12, 10, 11);
        $button->signal_connect('clicked' => sub {

            my (
                $state, $start, $middle, $end,
                @ivList,
            );

            $state = $combo->get_active_text();
            $start = $entry->get_text();
            if (! $start) {

                # Use an empty string, rather than 'undef'
                $start = '';
            }

            $middle = $entry2->get_text();
            if (! $middle) {

                $middle = '';
            }

            $end = $entry3->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(
            $table, 'Delete', 'Delete the selected exit state from the list', undef,
            8, 10, 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(
            $table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms14Tab_refreshList {

        # Resets the simple list displayed by $self->rooms14Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 . '->rooms15Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page 15', $innerNotebook);

        # Exit remove/info patterns
        $self->addLabel($table, '<b>Exit state/remove/info patterns</b>',
            0, 12, 0, 1);

        $self->addLabel(
            $table,
            '<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($table, 'exitStateStringList', TRUE,
            1, 12, 2, 5,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 80);                   # Fixed height

        $self->addLabel(
            $table,
            '<i>Parts of the exit which match these patterns are removed, before the exit is'
            . ' processed</i>',
            1, 12, 5, 6);
        $self->addTextView($table, 'exitRemovePatternList', TRUE,
            1, 12, 6, 9,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 80);                   # Fixed height

        $self->addLabel(
            $table,
            '<i>Parts of the exit which match these patterns are removed (but the first'
            . ' backreference is stored)</i>',
            1, 12, 9, 10);
        $self->addTextView($table, 'exitInfoPatternList', TRUE,
            1, 12, 10, 13,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 80);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms16Tab {

        # Rooms16 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 16', $innerNotebook);

        # Exit aliases
        $self->addLabel($table, '<b>Exit aliases</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of patterns, which appear in exit lists, but which represent one or more other'
            . ' exits</i>',
            1, 12, 1, 2);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 8,
            -1, 210);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Exit alias',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($table, 'Replacement string',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms17Tab {

        # Rooms17 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 17', $innerNotebook);

        # Contents list patterns
        $self->addLabel($table, '<b>Contents list patterns</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table, '<i>List of patterns seen in contents lists (which are not parsed)</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'contentPatternList', TRUE,
            1, 12, 4, 12,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 300);                  # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms18Tab {

        # Rooms18 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->rooms18Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page 18', $innerNotebook);

        # Status commands
        $self->addLabel($table, '<b>Special contents patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 6, 8, 9);

        $self->addLabel($table, 'Replacement',
            7, 8, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            8, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub rooms19Tab {

        # Rooms19 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 19', $innerNotebook);

        # Room command delimiter/marker strings
        $self->addLabel($table,
            '<b>Room command delimiter/marker strings</b>',
            0, 12, 0, 1);

        $self->addLabel($table, '<i>Delimiters (<b>not</b> regular expressions)</i>',
            1, 4, 1, 2);
        $self->addTextView($table, 'roomCmdDelimiterList', TRUE,
            1, 4, 2, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace
        $self->addLabel($table, '<i>Non-delimiters (<b>not</b> regular expressions)</i>',
            4, 8, 1, 2);
        $self->addTextView($table, 'roomCmdNonDelimiterList', TRUE,
            4, 8, 2, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace
        $self->addLabel($table, '<i>Ignorable room commands</i>',
            8, 12, 1, 2);
        $self->addTextView($table, 'roomCmdIgnoreList', TRUE,
            8, 12, 2, 11,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        $self->addLabel($table, '<i>Left-side marker patterns</i>',
            1, 4, 6, 7);
        $self->addTextView($table, 'roomCmdLeftMarkerList', TRUE,
            1, 4, 7, 11,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace
        $self->addLabel($table, '<i>Right-side marker patterns</i>',
            4, 8, 6, 7);
        $self->addTextView($table, 'roomCmdRightMarkerList', TRUE,
            4, 8, 7, 11,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        my $checkButton = $self->addCheckButton($table, 'roomCmdSplitCharFlag', TRUE,
            1, 12, 11, 12);
        $checkButton->set_label('Split room commands into single characters');
#        $self->addLabel(
#            $table,
#            'Split room commands into single characters',
#            2, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('Mo_ves', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Failed exit patterns
        $self->addLabel($table, '<b>Failed exit patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns seen when the character fails to move</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Dark room patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns seen when the character moves to a room with no light</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'darkRoomPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub moves2Tab {

        # Moves2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Door patterns
        $self->addLabel($table, '<b>Door patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns seen when the character bumps into a door</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Locked door patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Patterns seen when the character bumps into a locked door</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'lockedPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub moves3Tab {

        # Moves3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Involuntary exit patterns
        $self->addLabel($table, '<b>Involuntary exit patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns seen when the character is moved involuntarily</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Unspecified room patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns seen when the character moves to a room with no description</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'unspecifiedRoomPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub moves4Tab {

        # Moves4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->moves4Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Transient exit patterns
        $self->addLabel($table, '<b>Transient exit patterns</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table,
            '<i>Patterns matching exits which appear unpredictably (e.g. the entrance to a moving'
            . ' wagon)</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'transientExitPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub moves5Tab {

        # Moves5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Follow patterns
        $self->addLabel($table, '<b>Follow patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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 backref', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Follow pattern',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($table, 'Backreference for direction',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            9, 12, 10, 11);

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

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'followPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub moves6Tab {

        # Moves6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Follow patterns
        $self->addLabel($table, '<b>Follow anchor patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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 backref', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Follow anchor pattern',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($table, 'Backreference for direction',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            9, 12, 10, 11);

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

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'followAnchorPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('S_tatus', $self->notebook);

        # 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, $taskObj);
        $self->status6Tab($innerNotebook);
        $self->status7Tab($innerNotebook);
        $self->status8Tab($innerNotebook);
        $self->status9Tab($innerNotebook);
        $self->status10Tab($innerNotebook);
        $self->status11Tab($innerNotebook);
        $self->status12Tab($innerNotebook);
        $self->status13Tab($innerNotebook);

        return 1;
    }

    sub status1Tab {

        # Status1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Status task commands
        $self->addLabel($table, '<b>Status task commands</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Command',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($table, 'Sent how often (in seconds):',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status2Tab {

        # Status2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::Notebook object inside $self->notebook
        #   $taskObj        - A temporary Status task object
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Status task - display
        $self->addLabel($table, '<b>Status task - display</b>',
            0, 6, 0, 1);
        $self->addLabel(
            $table,
            '<i>How the Status task displays data for this world (format: @standard@,'
            . ' #custom# )</i>',
            1, 11, 1, 2);
        my $textView = $self->addTextView($table, 'displayFormatList', TRUE,
            1, 11, 2, 11,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 270);                  # Fixed height

        # Add a combo box displaying the list of Status task display variables
        my $comboBox = $self->addComboBox($table, undef, [$taskObj->displayVarList], '',
            TRUE,           # No 'undef' value used
            1, 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 = Gtk2::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 . '@');
        });
        $self->tooltips->set_tip($button, 'Insert the selected variable at the cursor');
        $table->attach_defaults($button, 3, 5, 11, 12);

        # Add a button to reset the contents of the textview to that stored by the profile
        my $button2 = Gtk2::Button->new('Reset');
        $button2->signal_connect('clicked' => sub {

            my $buffer = $textView->get_buffer();
            $buffer->set_text(join("\n", $self->editObj->displayFormatList));
        });
        $self->tooltips->set_tip($button2, 'Reset the Status task display');
        $table->attach_defaults($button2, 5, 7, 11, 12);

        # Add a button to use the Status task's default display format list
        my $button3 = Gtk2::Button->new('Default');
        $button3->signal_connect('clicked' => sub {

            my $buffer = $textView->get_buffer();
            $buffer->set_text(join("\n", $taskObj->defaultFormatList));
        });
        $self->tooltips->set_tip($button3, 'Use the default Status task display');
        $table->attach_defaults($button3, 7, 9, 11, 12);

        # Add a button to empty the textview
        my $button4 = Gtk2::Button->new('Clear');
        $button4->signal_connect('clicked' => sub {

            my $buffer = $textView->get_buffer();
            $buffer->set_text('');
        });
        $self->tooltips->set_tip($button4, 'Clear the Status task display');
        $table->attach_defaults($button4, 9, 11, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status3Tab {

        # Status3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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, @colourList);

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

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

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Status task - display
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 1, 6,
            -1, 120);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Variable:',
            1, 3, 6, 7);
        my $comboBox = $self->addComboBox($table, undef, [$taskObj->singleBackRefVarList], '',
            TRUE,               # No 'undef' value used
            3, 5, 6, 7);
        $self->addLabel($table, 'or custom:',
            5, 7, 6, 7);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            7, 9, 6, 7);

        $self->addLabel($table, 'Maximum var:',
            1, 3, 7, 8);
        my $comboBox2 = $self->addComboBox($table, undef, [$taskObj->singleBackRefVarList], '',
            TRUE,               # No 'undef' value used
            3, 5, 7, 8);
        $self->addLabel($table, 'or custom:',
            5, 7, 7, 8);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            7, 9, 7, 8);

        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            9, 12, 7, 8);
        $checkButton->set_label('Not maximum');

        @colourList = ($axmud::CLIENT->constColourTagList, $axmud::CLIENT->constBoldColourTagList);
        $self->addLabel($table, 'Full colour:',
            1, 3, 8, 9);
        my $comboBox3 = $self->addComboBox($table, undef, [@colourList], '',
            TRUE,               # No 'undef' value used
            3, 5, 8, 9);
        $self->addLabel($table, 'or xterm/RGB:',
            5, 7, 8, 9);
        my $entry3 = $self->addEntryWithIcon($table, undef, \&status3Tab_checkEntry, undef, undef,
            7, 9, 8, 9);

        $self->addLabel($table, 'Empty colour:',
            1, 3, 9, 10);
        my $comboBox4 = $self->addComboBox($table, undef, [@colourList], '',
            TRUE,               # No 'undef' value used
            3, 5, 9, 10);
        $self->addLabel($table, 'or xterm/RGB:',
            5, 7, 9, 10);
        my $entry4 = $self->addEntryWithIcon($table, undef, \&status3Tab_checkEntry, undef, undef,
            7, 9, 9, 10);

        $self->addLabel($table, 'Label colour:',
            1, 3, 10, 11);
        my $comboBox5 = $self->addComboBox($table, undef, [@colourList], '',
            TRUE,               # No 'undef' value used
            3, 5, 10, 11);
        $self->addLabel($table, 'or xterm/RGB:',
            5, 7, 10, 11);
        my $entry5 = $self->addEntryWithIcon($table, undef, \&status3Tab_checkEntry, undef, undef,
            7, 9, 10, 11);

        $self->addLabel($table, 'Label text <i>(optional)</i>:',
            9, 12, 9, 10);
        my $entry6 = $self->addEntry($table, undef, TRUE,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status3Tab_checkEntry {

        # Called by $self->status3Tab to check the text in the Gtk2::Entry
        #
        # Expected arguments
        #   $text       - The contents of the Gtk2::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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Status task custom variables
        $self->addLabel($table, '<b>Status task custom variables</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Custom variables that can be collected and displayed in the task window (use'
            . ' the #variable# format)</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Custom variable', 'text',
            'Corresponding MSDP variable (if any)', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 240);      # Fixed height

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

        # Add entries/comboboxes for adding new variables
        $self->addLabel($table, 'Custom variable',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($table, 'Equivalent MSDP variable (if any)',
            6, 9, 8, 9);
        my $entry2 = $self->addEntry($table, undef, TRUE,
            9, 12, 8, 9);

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

            my ($var, $msdpVar);

            $var = $entry->get_text();
            $msdpVar = $entry2->get_text();
            if (! $msdpVar) {

                # Use 'undef' rather than an empty string
                $msdpVar = undef;
            }

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

                # Add a new key-value pair
                $self->modifyEditHash_hashIV('customStatusVarHash', $var, $msdpVar);

                # Refresh the simple list and reset entry boxes
                $self->refreshList_hashIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'customStatusVarHash',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status5Tab {

        # Status5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->status5Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Status task - general patterns
        $self->addLabel($table, '<b>Status task - general patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns containing a single piece of data we need</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
            'Variable', 'text',
            'Mode', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 170);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($table, 'Backref #:',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            3, 6, 9, 10, 4, 4);

        $self->addLabel($table, 'Mode:',
            9, 10, 9, 10);
        my $comboBox = $self->addComboBox($table, undef, ['hide', 'show', 'choose'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        $self->addLabel($table, 'Standard or custom variable:',
            1, 6, 10, 11);

        $customString = 'Use a custom variable:';
        push (@comboList,
            $customString,
            $taskObj->singleBackRefVarList,
        );
        my $comboBox2 = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            6, 9, 10, 11);
        # ->signal_connect appears below

        my $entry3 = $self->addEntry($table, 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(
            $table,
            $slWidget,
            'singleBackRefPatternList',
            11, 4,
            $entry, $entry2, $entry3,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef, $var, $mode);

            $pattern = $entry->get_text();
            $backRef = $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('singleBackRefPatternList',
                    undef, FALSE,
                    $pattern, $backRef, $var, $mode
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status6Tab {

        # Status6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->status6Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Status task - bar patterns
        $self->addLabel($table, '<b>Status task - bar patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns containing data represented by multiple symbols</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
            'Variable', 'text',
            'Mode', 'text',
            'Unit', 'text',
            'Max units', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 210);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 6, 8, 9);

        $self->addLabel($table, 'Unit:',
            6, 7, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            7, 9, 8, 9, 4, 4);

        $self->addLabel($table, 'Max units:',
            9, 10, 8, 9);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            10, 12, 8, 9, 4, 4);

        $self->addLabel($table, 'Backref #:',
            1, 2, 9, 10);
        my $entry4 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            2, 4, 9, 10, 4, 4);

        $self->addLabel($table, 'Variable:',
            4, 6, 9, 10);
        my $comboBox = $self->addComboBox(
            $table,
            undef,
            ['health', 'magic', 'energy', 'guild', 'social', 'xp_current', 'xp_next_level'],
            '',
            TRUE,               # No 'undef' value used
            6, 8, 9, 10);

        $self->addLabel($table, 'Mode:',
            8, 10, 9, 10);
        my $comboBox2 = $self->addComboBox($table, undef, ['hide', 'show', 'choose'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'barPatternList',
            10, 6,
            $entry, $entry2, $entry3, $entry4,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef, $var, $mode, $unit, $maxUnits);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef, $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status7Tab {

        # Status7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Status task - character affect patterns
        $self->addLabel($table, '<b>Status task - character affect patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Patterns containing data about spells (etc) affecting the character</i>',
            0, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
            'Display', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 210);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 12, 8, 9);

        $self->addLabel($table, 'Backref #:',
            1, 2, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            2, 6, 9, 10, 4, 4);

        $self->addLabel($table, 'Mode:',
            9, 10, 9, 10);
        my $comboBox2 = $self->addComboBox($table, 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(
            $table,
            $slWidget,
            'affectPatternList',
            10,  4,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef, $mode);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef, $mode,
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status8Tab {

        # Status8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _8', $innerNotebook);

        # Status task - character stat patterns
        $self->addLabel($table, '<b>Status task - character stat patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns containing data about the character\'s stats</i>',
            0, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
            'Stat', 'text',
            'Display', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 210);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 12, 8, 9);

        $self->addLabel($table, 'Backref #:',
            1, 2, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            2, 6, 9, 10, 4, 4);

        $self->addLabel($table, 'Stat:',
            6, 7, 9, 10);
        @comboList = $self->editObj->ivKeys('statHash');
        my $comboBox = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            7, 9, 9, 10);

        $self->addLabel($table, 'Mode:',
            9, 10, 9, 10);
        my $comboBox2 = $self->addComboBox($table, 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(
            $table,
            $slWidget,
            'statPatternList',
            10,  4,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef, $stat, $mode);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef, $stat, $mode,
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status9Tab {

        # Status9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->status9Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _9', $innerNotebook);

        # Status task ignore patterns
        $self->addLabel($table, '<b>Status task ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns which should be ignored by the Status task</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Quest complete patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Patterns announcing a quest is complete</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'questCompletePatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status10Tab {

        # Status10 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 1_0', $innerNotebook);

        # Status task age patterns
        $self->addLabel($table, '<b>Status task age patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns containing the character\'s age</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Status task time patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Patterns containing the (in-game) time</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'timePatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status11Tab {

        # Status11 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 11', $innerNotebook);

        # Life patterns
        $self->addLabel($table, '<b>Life patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns seen when the character dies</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'deathPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        $self->addLabel($table, '<i>Patterns seen when the character is resurrected</i>',
            1, 12, 6, 8);
        $self->addTextView($table, 'resurrectPatternList', TRUE,
            1, 12, 8, 10,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status12Tab {

        # Status12 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 12', $innerNotebook);

        # Passing out patterns
        $self->addLabel($table, '<b>Passing out patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns seen when the character passes out</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'passedOutPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        $self->addLabel($table, '<i>Patterns seen when the character comes around</i>',
            1, 12, 6, 8);
        $self->addTextView($table, 'comeAroundPatternList', TRUE,
            1, 12, 8, 10,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub status13Tab {

        # Status13 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page 13', $innerNotebook);

        # Sleep patterns
        $self->addLabel($table, '<b>Sleep patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns seen when the character falls asleep</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'fallAsleepPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        $self->addLabel($table, '<i>Patterns seen when the character wakes up</i>',
            1, 12, 6, 8);
        $self->addTextView($table, 'wakeUpPatternList', TRUE,
            1, 12, 8, 10,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('Inventor_y', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Inventory collection mode
        $self->addLabel($table, '<b>Inventory collection mode</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Sets the way in which the Inventory task interprets the character\'s inventory</i>',
            1, 12, 1, 2);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'match_all\'', 'inventoryMode',
            'match_all',    # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 12, 2, 3);
        $self->addLabel($table, 'Only lines matching inventory task patterns (on Page 2) are used',
            2, 12, 3, 4);
        $self->addLabel(
            $table,
            'The inventory is reset whenever a line matching a start/stop pattern is found',
            2, 12, 4, 5);
        $self->addLabel(
            $table,
            '(so there must be at least one start pattern or one stop pattern)',
            3, 12, 5, 6);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'start_stop\'', 'inventoryMode',
            'start_stop',
            TRUE,
            1, 12, 6, 7);
        $self->addLabel(
            $table,
            '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(
            $table, $group, '\'start_empty\'', 'inventoryMode',
            'start_empty',
            TRUE,
            1, 12, 8, 9);
        $self->addLabel(
            $table,
            'All lines between a line matching a \'start\' pattern and the first empty line are'
            . ' used',
            2, 12, 9, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub inventory2Tab {

        # Inventory2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Inventory task patterns
        $self->addLabel($table, '<b>Inventory task patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns containing data about the character\'s inventory</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
            'Type', 'text',
            'Mode', 'text',
            'Position', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, '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($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 8, 9);

        $self->addLabel($table, 'Backref #:',
            1, 2, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            2, 6, 9, 10, 4, 4);

        $self->addLabel($table, 'Mode:',
            6, 7, 9, 10);
        my $comboBox2 = $self->addComboBox($table, undef, ['hide', 'show'], '',
            TRUE,               # No 'undef' value used
            7, 9, 9, 10);

        $self->addLabel($table, 'Position:',
            9, 10, 9, 10);
        my $comboBox3 = $self->addComboBox($table, undef, ['start', 'stop', 'optional'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'inventoryPatternList',
            10,  5,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef, $type, $mode, $posn);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef, $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub inventory3Tab {

        # Inventory3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Inventory task ignore patterns
        $self->addLabel($table, '<b>Inventory task ignore patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 10, 11);

        $self->addLabel($table, 'Mode:',
            8, 10, 10, 11);
        my $comboBox = $self->addComboBox($table, undef, ['hide', 'show'], '',
            TRUE,               # No 'undef' value used
            10, 12, 10, 11);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub inventory4Tab {

        # Inventory4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Inventory discard patterns
        $self->addLabel($table, '<b>Inventory discard patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Patterns matching portions of an inventory line that should be discarded</i>',
            1, 12, 1, 2);

        $self->addTextView($table, 'inventoryDiscardPatternList', TRUE,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                   # Fixed height

        # Inventory split patterns
        $self->addLabel($table, '<b>Inventory split patterns</b>',
            0, 12, 6, 7);
        $self->addLabel($table,
            '<i>Patterns matching portions of an inventory line between distinct objects</i>',
            1, 12, 7, 8);

        $self->addTextView($table, 'inventorySplitPatternList', TRUE,
            1, 12, 8, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 130);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub inventory5Tab {

        # Inventory5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Condition task patterns
        $self->addLabel($table, '<b>Condition task patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Patterns containing data about an inventory object\'s condition</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
            'Mode', 'text',
            'Condition', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 12, 8, 9);

        $self->addLabel($table, 'Backref #:',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            3, 5, 9, 10, 4, 4);

        $self->addLabel($table, 'Condition (0-100):',
            5, 7, 9, 10);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'int', 0, 100,
            7, 9, 9, 10, 3, 3);

        $self->addLabel($table, 'Mode:',
            9, 10, 9, 10);
        my $comboBox = $self->addComboBox($table, undef, ['hide', 'show'], '',
            TRUE,               # No 'undef' value used
            10, 12, 9, 10);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'conditionPatternList',
            10,  4,
            $entry, $entry2, $entry3
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef, $mode, $condition);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef, $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub inventory6Tab {

        # Inventory6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Condition task ignore patterns
        $self->addLabel($table, '<b>Condition task ignore patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns which should be ignored by the Condition task</i>',
            1, 12, 1, 2);
        $self->addTextView($table, 'conditionIgnorePatternList', TRUE,
            1, 12, 2, 12,
            TRUE, TRUE, FALSE, FALSE,   # Treat as list, remove empty lines, don't remove whitespace
            -1, 300);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub inventory7Tab {

        # Inventory7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Inventory commands
        $self->addLabel($table, '<b>Inventory commands</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 8,
            -1, 220);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Command',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($table, 'Sent how often (in seconds):',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('C_hannels', $self->notebook);

        # Add tabs to the inner notebook
        $self->channels1Tab($innerNotebook);
        $self->channels2Tab($innerNotebook);

        return 1;
    }

    sub channels1Tab {

        # Channels1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Channel patterns
        $self->addLabel($table, '<b>Channel patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 9,
            -1, 220);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 12, 9, 10);

        $self->addLabel($table, 'Channel name (1-16 chars):',
            1, 2, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, 16,
            2, 6, 10, 11,
            16, 16);

        $self->addLabel($table, 'Only in task window?',
            6, 11, 10, 11);
        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            11, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $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($table,
            '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->showDoubleComboDialogue(
                '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub channels2Tab {

        # Channels2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Channel ignore patterns
        $self->addLabel($table, '<b>Channel ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which match text that should not be diverted to the Channels or Divert'
            . ' tasks</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'noChannelList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE); # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('Attac_k', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Target leaves patterns
        $self->addLabel($table, '<b>Target leaves patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            4, 6, 9, 10, 4, 4);

        $self->addLabel($table, 'Direction backref # (0 if unspecified):',
            7, 10, 9, 10);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            10, 12, 9, 10, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'targetLeavesPatternList',
            10,  3,
            $entry, $entry2, $entry3
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $targetBackRef, $dirBackRef);

            $pattern = $entry->get_text();
            $targetBackRef = $entry2->get_text();
            $dirBackRef = $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, $targetBackRef, $dirBackRef
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attack2Tab {

        # Attack2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Target arrives patterns
        $self->addLabel($table, '<b>Target arrives patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            1, 4, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            4, 6, 9, 10, 4, 4);

        $self->addLabel($table, 'Direction backref # (0 if unspecified):',
            6, 10, 9, 10);
        my $entry3 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            10, 12, 9, 10, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'targetArrivesPatternList',
            10,  3,
            $entry, $entry2, $entry3
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $targetBackRef, $dirBackRef);

            $pattern = $entry->get_text();
            $targetBackRef = $entry2->get_text();
            $dirBackRef = $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, $targetBackRef, $dirBackRef
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attack3Tab {

        # Attack3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Target leaves ignore patterns
        $self->addLabel($table, '<b>Target leaves ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean a target (or potential target) didn\'t just leave</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'noTargetLeavesPatternList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Target arrives patterns
        $self->addLabel($table, '<b>Target arrives ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns which mean a target (or potential target) didn\'t just arrive</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noTargetArrivesPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attack4Tab {

        # Attack4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # No fights room patterns
        $self->addLabel($table, '<b>No fights room patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns seen when the character initiates a fight in a room which forbids'
            . ' them</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>No interactions room patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns seen when the character initiates an interaction in a room which forbids'
            . ' them</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noInteractionsRoomPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Fight', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Fight started patterns
        $self->addLabel($table, '<b>Fight started patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen when the character initiates a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'fightStartedPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef,
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight2Tab {

        # WorldFight2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Cannot find target patterns
        $self->addLabel($table, '<b>Cannot find target patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'cannotFindTargetPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'cannotFindTargetPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight3Tab {

        # WorldFight3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Fight started ignore patterns
        $self->addLabel($table, '<b>Fight started ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns which mean the character didn\'t initiate a fight</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Target not found ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns which mean the character couldn\'t fight a non-existent target</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noCannotFindTargetPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight4Tab {

        # WorldFight4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Target already dead patterns
        $self->addLabel($table, '<b>Target already dead patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'targetAlreadyDeadPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'targetAlreadyDeadPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight5Tab {

        # WorldFight5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Target killed patterns
        $self->addLabel($table, '<b>Target killed patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen when the character wins a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'targetKilledPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef,
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'targetKilledPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight6Tab {

        # WorldFight6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Target already dead ignore patterns
        $self->addLabel($table, '<b>Target already dead ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns which mean the character didn\'t fight a corpse</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Target killed ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Patterns which mean the character didn\'t just win a fight</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noTargetKilledPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight7Tab {

        # WorldFight7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Target defeat patterns
        $self->addLabel($table, '<b>Target defeat patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'fightDefeatPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'fightDefeatPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight8Tab {

        # WorldFight8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _8', $innerNotebook);

        # Target killed patterns
        $self->addLabel($table, '<b>Remote wimpy engaged patterns</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'wimpyEngagedPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'wimpyEngagedPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldFight9Tab {

        # WorldFight9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _9', $innerNotebook);

        # Target defeat ignore patterns
        $self->addLabel($table, '<b>Target defeat ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns which mean the character didn\'t lose a fight</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Remote wimpy engaged ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Patterns which mean the world didn\'t engage wimpy mode</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noWimpyEngagedPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('Int_eraction', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Interaction started patterns
        $self->addLabel($table, '<b>Interaction started patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen when the character initiates an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'interactionStartedPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction2Tab {

        # WorldInteraction2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Cannot interact patterns
        $self->addLabel($table, '<b>Cannot interact patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Patterns seen when the character fails to initiate an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'cannotInteractPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'cannotInteractPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction3Tab {

        # WorldInteraction3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Interaction started ignore patterns
        $self->addLabel($table, '<b>Interaction started ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean the character didn\'t initiate an interaction</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Cannot interact ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns which mean the character didn\'t fail to initiate an interaction</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noCannotInteractPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction4Tab {

        # WorldInteraction4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Interaction success patterns
        $self->addLabel($table, '<b>Interaction success patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen when the character wins an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add buttons at various positions
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'interactionSuccessPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionSuccessPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction5Tab {

        # WorldInteraction5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Interaction fail patterns
        $self->addLabel($table, '<b>Interaction fail patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen when the character loses an interaction</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'interactionFailPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction6Tab {

        # WorldInteraction6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Interaction success ignore patterns
        $self->addLabel($table, '<b>Interaction success ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns seen when the character didn\'t just win an interaction</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Interaction fail ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns seen when the character didn\'t just lose an interaction</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noInteractionFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction7Tab {

        # WorldInteraction7 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _7', $innerNotebook);

        # Interaction conversion patterns
        $self->addLabel($table, '<b>Interaction conversion patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Patterns seen when an interaction turns into a fight</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'interactionFightPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionFightPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction8Tab {

        # WorldInteraction8 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _8', $innerNotebook);

        # Interaction disaster patterns
        $self->addLabel($table, '<b>Interaction disaster patterns</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>Patterns seen when an interaction requires the character to flee</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Backref #', 'int',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Pattern:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Target backref #:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_listIV(
            $table,
            $slWidget,
            'interactionDisasterPatternList',
            9, 2,
            $entry, $entry2,
        );
        $button->signal_connect('clicked' => sub {

            my ($pattern, $backRef);

            $pattern = $entry->get_text();
            $backRef = $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, $backRef
                );

                # Refresh the simple list and reset entry boxes
                $self->refreshList_listIV(
                    $slWidget,
                    scalar (@columnList / 2),
                    'interactionDisasterPatternList',
                );

                $self->resetEntryBoxes($entry, $entry2);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldInteraction9Tab {

        # WorldInteraction9 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _9', $innerNotebook);

        # Interaction conversion ignore patterns
        $self->addLabel($table, '<b>Interaction conversion ignore patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns seen when an interaction didn\'t just turn into a fight</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Interaction disaster ignore patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns seen when an interaction didn\'t just require the character to flee</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'noInteractionDisasterPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Commands', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Get success patterns
        $self->addLabel($table, '<b>Get success patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'get axe\' was successful</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Get heavy patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'get axe\' failed because the object was too'
            . ' heavy</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'getHeavyPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldCommands2Tab {

        # WorldCommands2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Get fail patterns
        $self->addLabel($table, '<b>Get fail patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'get axe\' failed because there is such object'
            . ' to get</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Drop success patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'drop axe\' was successful</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'dropSuccessPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldCommands3Tab {

        # WorldCommands3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Drop forbid patterns
        $self->addLabel($table, '<b>Drop forbid patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'drop axe\' failed because the world forbids'
            . ' it</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Drop fail patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<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($table, 'dropFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldCommands4Tab {

        # WorldCommands4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Buy success patterns
        $self->addLabel($table, '<b>Buy success patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'buy axe\' was successful</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Buy partial success patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'buy ten axes\' partially succeeded'
            . ' (e.g. bought five)</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'buyPartialPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldCommands5Tab {

        # WorldCommands5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Buy fail patterns
        $self->addLabel($table, '<b>Buy fail patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'buy axe\' failed because there is no such'
            . ' object to buy</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Sell success patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'sell axe\' was successful</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'sellSuccessPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub worldCommands6Tab {

        # WorldCommands6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Sell partial success patterns
        $self->addLabel($table, '<b>Sell partial success patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Patterns which mean a command like \'sell ten axes\' partially succeeded (e.g.'
            . ' sold five)</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Sell fail patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<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($table, 'sellFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Advance', $self->notebook);

        # Advance success patterns
        $self->addLabel($table, '<b>Advance success patterns</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Patterns seen when the character advances their skills</i>',
            1, 12, 2, 4);
        $self->addTextView($table, '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($table, '<b>Advance fail patterns</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<i>Patterns seen when the character fails to advance their skills</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'advanceFailPatternList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, FALSE, FALSE);  # Treat as list, remove empty lines, don't remove whitespace

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('C_urrency', $self->notebook);

        # Currency conversion chart
        $self->addLabel($table, '<b>Currency conversion chart</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Conversion into the standard unit:',
            1, 6, 1, 2);
        @comboList = $self->editObj->ivKeys('currencyHash');
        my $comboBox = $self->addComboBox($table, 'standardCurrencyUnit', \@comboList, '',
            FALSE,              # 'undef' value allowed
            6, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Currency unit', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Currency unit:',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($table, 'Value (in standard units):',
            6, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'float', 0, undef,
            9, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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($table,
            'Round cash values to this many decimal places (0-9, or -1 to prevent rounding)',
            1, 9, 10, 11);
        $self->addEntryWithIcon($table, 'currencyRounding', 'int', -1, 9,
            9, 12, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Missions', $self->notebook);

        # Missions list
        $self->addLabel($table, '<b>Missions list</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 9,
            -1, 270);      # Fixed height

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

        # Add some buttons and a single entry box
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 3, 16,
            1, 4, 9, 10);
        my $button = $self->addButton($table,
            '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($table, '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($table, '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($table,
            '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($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub missionsTab_refreshList {

        # Resets the simple list displayed by $self->missionsTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 ($vBox, $table) = $self->addTab('_Quests', $self->notebook);

        # Quest list
        $self->addLabel($table, '<b>Quest list</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Number of quests listed',
            1, 4, 1, 2);
        my $entry = $self->addEntry($table, 'questCount', FALSE,
            4, 6, 1, 2, 4, 4);
        $self->addLabel($table, 'Total quest points',
            7, 10, 1, 2);
        my $entry2 = $self->addEntry($table, 'questPointCount', FALSE,
            10, 12, 1, 2, 8, 8);

        $self->addLabel($table, 'Total quest XP',
            1, 4, 2, 3);
        my $entry3 = $self->addEntry($table, 'questXPCount', FALSE,
            4, 6, 2, 3, 16, 16);
        $self->addLabel($table, 'Total quest cash',
            7, 10, 2, 3);
        my $entry4 = $self->addEntry($table, '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($table, undef, \@columnList,
            1, 12, 3, 10,
            -1, 230);     # Fixed height
        # Initialise the list
        $self->questsTab_refreshList($slWidget, (scalar @columnList / 2));

        # Add some buttons and a single entry box
        my $entry5 = $self->addEntryWithIcon($table, undef, 'string', 3, 16,
            1, 4, 10, 11);
        my $button = $self->addButton($table, '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($table, '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($table, '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($table,
            '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($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub questsTab_refreshList {

        # Resets the simple list displayed by $self->questsTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Stats', $self->notebook);

        # Add tabs to the inner notebook
        $self->stats1Tab($innerNotebook);
        $self->stats2Tab($innerNotebook);

        return 1;
    }

    sub stats1Tab {

        # Stats1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Character stats
        $self->addLabel($table, '<b>Character stats</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Stat',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Default value (usually 0):',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub stats2Tab {

        # Stats2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Character stats (2)
        $self->addLabel($table, '<b>Character stats (2)</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table,
            '<i>Optional list of stats - if set, the Status task displays stats in this order</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'statOrderList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE); # Treat as list, remove empty lines, remove whitespace
        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   1 otherwise

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

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

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

        return $axmud::CLIENT->ipv4Check($ipv4);
    }

    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
        #   1 otherwise

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

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

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

        return $axmud::CLIENT->ipv6Check($ipv6);
    }

    ##################
    # 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Skills', $self->notebook);

        # Add tabs to the inner notebook
        $self->skills1Tab($innerNotebook);
        $self->skills2Tab($innerNotebook);

        return 1;
    }

    sub skills1Tab {

        # Skills1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # List of guild skills
        $self->addLabel($table, '<b>List of guild skills</b>',
            0, 10, 0, 1);
        my $button = $self->addButton($table,
            '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($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Skill:',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Max level:',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            9, 12, 8, 9);

        # Add buttons at various positions
        my $button2 = Gtk2::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);
            }
        });
        $self->tooltips->set_tip($button, 'Add a new pattern with the above settings');
        $table->attach_defaults($button2, 1, 3, 9, 10);

        my $button3 = Gtk2::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);
            }
        });
        $self->tooltips->set_tip($button3, 'Delete the selected pattern');
        $table->attach_defaults($button3, 6, 8, 9, 10);

        my $button4 = Gtk2::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);
        });
        $self->tooltips->set_tip($button4, 'Reset the list of patterns');
        $table->attach_defaults($button4, 8, 10, 9, 10);

        my $button5 = Gtk2::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);
        });
        $self->tooltips->set_tip($button5, 'Clear the list of patterns');
        $table->attach_defaults($button5, 10, 12, 9, 10);

        # 'Dump' button can't be manipulated when this isn't a current profile
        if (! $self->currentFlag) {

            $button->set_sensitive(FALSE);
        }

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub skills2Tab {

        # Skills2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Skills advance list
        $self->addLabel($table, '<b>Skills advance list</b>',
            0, 6, 0, 2);
        $self->addLabel($table, '<i>Pre-determined list of skills to advance</i>',
            1, 6, 2, 4);
        $self->addTextView($table, 'advanceOrderList', TRUE,
            1, 12, 4, 6);

        # Skills advance cycle
        $self->addLabel($table, '<b>Skills advance cycle</b>',
            0, 12, 6, 8);
        $self->addLabel($table, '<i>Pre-determined cycle of skills to advance</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'advanceCycleList', TRUE,
            1, 12, 10, 12);

        $self->addLabel($table, '<b>Default advance method</b>',
            6, 12, 0, 2);
        @comboList = ('order', 'cycle', 'combo');
        $self->addComboBox($table, 'advanceMethod', \@comboList, '',
            TRUE,               # No 'undef' value used
            7, 12, 2, 4);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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);
                }
            }

            # 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 object 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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Settings', $self->notebook);

        # Add tabs to the inner notebook
        $self->settings1Tab($innerNotebook);
        $self->settings2Tab($innerNotebook);
        $self->settings3Tab($innerNotebook);

        return 1;
    }

    sub settings1Tab {

        # Settings1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # 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($table, '<b>Character settings</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Guild',
            1, 3, 1, 2);
        $self->addComboBox($table, 'guild', \@guildList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 1, 2);
        $self->addLabel($table, 'Race',
            1, 3, 2, 3);
        $self->addComboBox($table, 'race', \@raceList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);
        $self->addLabel($table, 'Current XP',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'xpCurrent', 'int', 0, undef,
            3, 6, 3, 4);
        $self->addLabel($table, 'XP to next level',
            1, 3, 4, 5);
        $self->addEntryWithIcon($table, 'xpNextLevel', 'int', 0, undef,
            3, 6, 4, 5);
        $self->addLabel($table, 'Total XP',
            1, 3, 5, 6);
        $self->addEntryWithIcon($table, 'xpTotal', 'int', 0, undef,
            3, 6, 5, 6);
        $self->addLabel($table, 'Age',
            1, 3, 6, 7);
        $self->addEntryWithIcon($table, 'age', 'int', 0, undef,
            3, 6, 6, 7);
        $self->addLabel($table, 'Level',
            1, 3, 7, 8);
        $self->addEntryWithIcon($table, 'level', 'int', 0, undef,
            3, 6, 7, 8);

        # Right column
        $self->addLabel($table, 'Bank balance',
            7, 9, 1, 2);
        $self->addEntryWithIcon($table, 'bankBalance', 'int', 0, undef,
            9, 12, 1, 2);
        $self->addLabel($table, 'Purse contents',
            7, 9, 2, 3);
        $self->addEntryWithIcon($table, 'purseContents', 'int', 0, undef,
            9, 12, 2, 3);
        $self->addLabel($table, 'Lives left',
            7, 9, 3, 4);
        $self->addEntryWithIcon($table, 'lifeCount', 'int', 0, undef,
            9, 12, 3, 4);
        $self->addLabel($table, 'Times killed',
            7, 9, 4, 5);
        $self->addEntryWithIcon($table, 'deathCount', 'int', 0, undef,
            9, 12, 4, 5);
        $self->addLabel($table, 'Max lives',
            7, 9, 5, 6);
        $self->addEntryWithIcon($table, 'lifeMax', 'int', 0, undef,
            9, 12, 5, 6);
        $self->addLabel($table, 'Life status',
            7, 9, 6, 7);
        @comboList = ('alive', 'sleep', 'passout', 'dead');
        $self->addComboBox($table, 'lifeStatus', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 6, 7);
        $self->addLabel($table, 'Alignment',
            7, 9, 7, 8);
        $self->addEntryWithButton($table, 'alignment', TRUE,
            9, 12, 7, 8);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->settings2Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Import the current world
        $worldObj = $self->session->currentWorld;

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

        $self->addLabel($table, 'Current world wimpy',
            1, 3, 1, 2);
        $self->addEntryWithIcon($table, 'remoteWimpy', 'int', 0, $worldObj->remoteWimpyMax,
            3, 6, 1, 2);
        $self->addLabel($table, 'Maximum world wimpy',
            7, 9, 1, 2);
        my $entry = $self->addEntry($table, undef, FALSE,
            9, 12, 1, 2);
        $entry->set_text($worldObj->remoteWimpyMax);

        $self->addLabel($table, 'Current ' . $axmud::SCRIPT . ' wimpy',
            1, 3, 2, 3);
        $self->addEntryWithIcon($table, 'localWimpy', 'int', 0, $worldObj->constLocalWimpyMax,
            3, 6, 2, 3);
        $self->addLabel($table, 'Maximum ' . $axmud::SCRIPT . ' wimpy',
            7, 9, 2, 3);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            9, 12, 2, 3);
        $entry2->set_text($worldObj->constLocalWimpyMax);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings3Tab {

        # Settings3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->settings3Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Custom profile hash
        $self->addLabel($table, '<b>Custom profile hash</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);     # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton(
            $table,
            'Refresh',
            'Refresh the list of custom profiles',
            undef,
            10, 12, 10, 11,
        );
        $button->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->settings3Tab_refreshList($slWidget, scalar (@columnList / 2));
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings3Tab_refreshList {

        # Resets the simple list displayed by $self->settings3Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 . '->settings3Tab_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 ($vBox, $table) = $self->addTab('_Quests', $self->notebook);

        # Character quests
        $self->addLabel($table, '<b>Character quests</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'No. of quests',
            1, 4, 1, 2);
        my $entry = $self->addEntry($table, undef, FALSE,
            4, 6, 1, 2, 4, 4);
        $self->addLabel($table, 'Quest points',
            1, 4, 2, 3);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            4, 6, 2, 3, 8, 8);
        $self->addLabel($table, 'Quest XP',
            1, 4, 3, 4);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            4, 6, 3, 4, 16, 16);
        $self->addLabel($table, 'Quest cash',
            1, 4, 4, 5);
        my $entry4 = $self->addEntry($table, undef, FALSE,
            4, 6, 4, 5, 16, 16);

        # World quests
        $worldObj = $self->session->ivShow('profHash', $self->editObj->parentWorld);

        $self->addLabel($table, '<b>World quests</b>',
            7, 13, 0, 1);

        $self->addLabel($table, 'No. of quests',
            8, 11, 1, 2);
        my $entry5 = $self->addEntry($table, undef, 0,
            11, 13, 1, 2, 4, 4);

        $self->addLabel($table, 'Quest points',
            8, 11, 2, 3);
        my $entry6 = $self->addEntry($table, undef, 0,
            11, 13, 2, 3, 8, 8);

        $self->addLabel($table, 'Quest XP',
            8, 11, 3, 4);
        my $entry7 = $self->addEntry($table, undef, 0,
            11, 13, 3, 4, 16, 16);

        $self->addLabel($table, 'Quest cash',
            8, 11, 4, 5);
        my $entry8 = $self->addEntry($table, undef, 0,
            11, 13, 4, 5, 16, 16);

        # Solved/unsolved quests
        $self->addLabel($table, '<b>Solved quests</b>',
            0, 6, 5, 6);
        $self->addLabel($table, '<b>Unsolved quests</b>',
            7, 13, 5, 6);

        # Add a simple list for solved quests
        @columnList = (
            'Quest name', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 6, 6, 11,
            -1, 180);      # Fixed height

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

        # Add a simple list of unsolved quests
        my $slWidget2 = $self->addSimpleList($table, undef, \@columnList,
            8, 13, 6, 11,
            -1, 180);     # Fixed height

        # Initialise the list
        $self->questsTab_resetUnsolvedList($slWidget2, (scalar @columnList / 2), $worldObj);

        # Add buttons
        my $button = $self->addButton($table,
            '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($table,
            '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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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::Gtk::Simple::List
        #   $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::Gtk::Simple::List
        #   $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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('S_kills', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Character skills
        $self->addLabel($table, '<b>Character skills</b>',
            0, 10, 0, 1);
        my $button = $self->addButton($table,
            '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($table, 'Advance method',
            1, 3, 1, 2);
        @comboList = ('order', 'cycle', 'combo');
        my $combo = $self->addComboBox($table, 'advanceMethod', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 1, 2);

        $self->addLabel($table, 'Combo method status',
            7, 9, 1, 2);
        my $entry = $self->addEntry($table, 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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 210);     # Fixed height

        # 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($table,
            '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($table, undef, 'int', 1, 100,
            3, 6, 10, 11);
        $entry2->set_text('1');
        $self->addLabel($table, '...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($table,
            '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($table, undef, \@comboList2, '',
            FALSE,              # 'undef' value allowed
            1, 4, 11, 12);

        my $button4 = $self->addButton($table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub skills1Tab_refreshList {

        # Resets the simple list displayed by $self->skills1Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Character skill history
        $self->addLabel($table, '<b>Character skill history</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);     # Fixed height

        # 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($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub skills3Tab {

        # Skills3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Advance skills - ordered list
        $self->addLabel($table, '<b>Advance skills - ordered list</b>',
            0, 10, 0, 2);
        my $button = $self->addButton($table,
            '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($table,
            '<i>Pre-determined list of skills to advance (empty if not used)</i>',
            1, 10, 2, 4);
        my $textView = $self->addTextView($table, 'advanceOrderList', TRUE,
            1, 12, 4, 6);
        my $buffer = $textView->get_buffer();

        $self->addLabel($table,
            '<i>Current list of skills to advance (emptied as they are used up)</i>',
            1, 10, 8, 10);
        my $button2 = $self->addButton($table,
            'Skip next', 'Skip the next skill in the list below', undef,
            10, 12, 8, 10,
            TRUE,           # Irreversible
        );

        my $textView2 = $self->addTextView($table, 'currentAdvanceOrderList', TRUE,
            1, 12, 10, 12);
        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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub skills4Tab {

        # Skills4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Advance skills - cycle list
        $self->addLabel($table, '<b>Advance skills - cycle list</b>',
            0, 10, 0, 2);
        my $button = $self->addButton($table,
            '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($table,
            '<i>Pre-determined list of skills to advance (empty if not used)</i>',
            1, 12, 2, 4);
        my $textView = $self->addTextView($table, 'advanceCycleList', TRUE,
            1, 12, 4, 6);
        my $buffer = $textView->get_buffer();

        $self->addLabel($table,
            '<i>Current cycle of skills to advance (list refreshed when they are used up)</i>',
            1, 10, 8, 10);
        my $button2 = $self->addButton($table,
            'Skip next', 'Skip the next skill in the list below', undef,
            10, 12, 8, 10,
            TRUE,           # Irreversible
        );

        my $textView2 = $self->addTextView($table, 'currentAdvanceCycleList', TRUE,
            1, 12, 10, 12);
        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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Attacks', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Fight statistics
        $self->addLabel($table, '<b>Fight statistics</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'No. fights',
            1, 3, 1, 2);
        $self->addEntryWithIcon($table, 'fightCount', 'int', 0, undef,
            3, 6, 1, 2);
        $self->addLabel($table, 'No. kills',
            1, 3, 2, 3);
        $self->addEntryWithIcon($table, 'killCount', 'int', 0, undef,
            3, 6, 2, 3);
        $self->addLabel($table, 'No. wimpys',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'wimpyCount', 'int', 0, undef,
            3, 6, 3, 4);
        $self->addLabel($table, 'No. fight defeats',
            1, 3, 4, 5);
        $self->addEntryWithIcon($table, 'fightDefeatCount', 'int', 0, undef,
            3, 6, 4, 5);

        # Coward statistics
        $self->addLabel($table, '<b>Coward statistics</b>',
            0, 6, 6, 7);
        $self->addLabel($table, 'No. targets fled',
            1, 3, 7, 8);
        $self->addEntryWithIcon($table, 'fleeCount', 'int', 0, undef,
            3, 6, 7, 8);
        $self->addLabel($table, 'No. targets escaped',
            1, 3, 8, 9);
        $self->addEntryWithIcon($table, 'escapeCount', 'int', 0, undef,
            3, 6, 8, 9);

        # Interaction statistics
        $self->addLabel($table, '<b>Interaction statistics</b>',
            6, 12, 0, 1);
        $self->addLabel($table, 'No. interactions',
            7, 9, 1, 2);
        $self->addEntryWithIcon($table, 'interactCount', 'int', 0, undef,
            9, 12, 1, 2);
        $self->addLabel($table, 'No. successes',
            7, 9, 2, 3);
        $self->addEntryWithIcon($table, 'interactSuccessCount', 'int', 0, undef,
            9, 12, 2, 3);
        $self->addLabel($table, 'No. fails',
            7, 9, 3, 4);
        $self->addEntryWithIcon($table, 'interactFailCount', 'int', 0, undef,
            9, 12, 3, 4);
        $self->addLabel($table, 'No. fight conversions',
            7, 9, 4, 5);
        $self->addEntryWithIcon($table, 'interactFightCount', 'int', 0, undef,
            9, 12, 4, 5);
        $self->addLabel($table, 'No. disasters',
            7, 9, 5, 6);
        $self->addEntryWithIcon($table, 'interactDisasterCount', 'int', 0, undef,
            9, 12, 5, 6);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attacks2Tab {

        # Attacks2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Character stats
        $self->addLabel($table, '<b>Fight history</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new records
        $self->addLabel($table, 'Main noun',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Number of kills',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attacks3Tab {

        # Attacks3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Character stats
        $self->addLabel($table, '<b>Fight history</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new records
        $self->addLabel($table, 'Base string',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Number of kills',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attacks4Tab {

        # Attacks4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Character stats
        $self->addLabel($table, '<b>Interaction history</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new records
        $self->addLabel($table, 'Main noun',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Success count',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub attacks5Tab {

        # Attacks5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Character stats
        $self->addLabel($table, '<b>Interaction history</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);      # Fixed height

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

        # Add entries/comboboxes for adding new records
        $self->addLabel($table, 'Base string',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Success count',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('A_ffects', $self->notebook);

        # Character
        $self->addLabel($table, '<b>Character affects</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 270);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Affect',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, '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($table, undef, 'float', 0, undef,
#           10, 12, 8, 9);
        $self->addEntry($table, undef, FALSE,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('S_tats', $self->notebook);

        # Character stats
        $self->addLabel($table, '<b>Character stats</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 270);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Stat',
            1, 4, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            4, 7, 8, 9);

        $self->addLabel($table, 'Current value',
            7, 10, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'float', 0, undef,
            10, 12, 8, 9);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('Stat_us', $self->notebook);

        # Status commands
        $self->addLabel($table, '<b>Status commands</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of commands sent to the world to update the Status task\'s variables</i>',
            1, 12, 1, 2);
        $self->addCheckButton($table, 'statusCmdFlag', TRUE,
            1, 2, 2, 3);
        $self->addLabel($table, 'Send commands as soon as the Status task is initialised',
            2, 12, 2, 3);

        # Add a simple list
        @columnList = (
            'Command', 'text',
            'How often', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 3, 10,
            -1, 250);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Command',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($table, 'Sent how often (in seconds):',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('In_ventory', $self->notebook);

        # Inventory commands
        $self->addLabel($table, '<b>Inventory commands</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);      # Fixed height

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

        # Add entries/comboboxes for adding new patterns
        $self->addLabel($table, 'Command',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 10, 11);

        $self->addLabel($table, 'Sent how often (in seconds):',
            6, 9, 10, 11);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('O_bjects', $self->notebook);

        # 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 ($vBox, $table) = $self->addTab('_Category', $self->notebook);

        # Profile template category
        $self->addLabel($table, '<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($table, '',
            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($table, 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($table, '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($table);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 object 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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Category', $self->notebook);

        # Fixed? (No more changes permitted)
        $self->addLabel($table, '<b>Fixed? (No more changes permitted)</b>',
            7, 13, 0, 1);

        my $checkbutton = Gtk2::CheckButton->new();
        if ($self->editObj->constFixedFlag) {

            $checkbutton->set_active(TRUE);
        }
        $checkbutton->set_sensitive(FALSE);
        $table->attach_defaults($checkbutton, 7, 13, 1, 2);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('P_roperties', $self->notebook);

        # Profile template properties
        $self->addLabel($table, '<b>Profile template properties</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 220);      # Fixed height

        # 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($table, '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($table, 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($table, '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($table, '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($table, '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 = Gtk2::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),
                    );
                }
            }
        });
        $self->tooltips->set_tip($button4, 'Edit the selected propery');
        $table->attach_defaults($button4, 1, 4, 9, 10);

        my $button5 = Gtk2::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));
            }
        });
        $self->tooltips->set_tip($button5, 'Delete the selected property');
        $table->attach_defaults($button5, 6, 8, 9, 10);

        my $button6 = Gtk2::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));
        });
        $self->tooltips->set_tip($button6, 'Reset the list of properties');
        $table->attach_defaults($button6, 8, 10, 9, 10);

        my $button7 = Gtk2::Button->new('Clear');
        $button7->signal_connect('clicked' => sub {

            $self->ivEmpty('editHash');

            # Update the simple list
            $self->propertiesTab_refreshList($slWidget, scalar (@columnList / 2));
        });
        $self->tooltips->set_tip($button7, 'Clear the list of properties');
        $table->attach_defaults($button7, 10, 12, 9, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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::Gtk::Simple::List 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::Gtk::Simple::list
        #   $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 ($vBox, $table) = $self->addTab('P_roperties', $self->notebook);

        # Custom properties
        $self->addLabel($table, '<b>Custom properties</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 220);      # Fixed height

        # 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 = Gtk2::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),
                    );
                }
            }
        });
        $self->tooltips->set_tip($button4, 'Edit the selected propery');
        $table->attach_defaults($button4, 1, 4, 10, 11);

        my $button6 = Gtk2::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));
        });
        $self->tooltips->set_tip($button6, 'Reset the list of properties');
        $table->attach_defaults($button6, 8, 12, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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::Gtk::Simple::List 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::Gtk::Simple::list
        #   $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 ($vBox, $table) = $self->addTab('_Nouns', $self->notebook);

        # Set up the rest of the tab
        $self->nounsTab($table);

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Nouns', $self->notebook);

        # Nouns
        $self->addLabel($table, '<b>Nouns</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Main noun</i>',
            1, 3, 1, 2);
        $self->addEntryWithIcon($table, 'noun', 'string', 1, undef,
            3, 6, 1, 2);

        $self->addLabel($table, '<i>List of other nouns</i>',
            1, 12, 2, 3);
        $self->addTextView($table, 'otherNounList', TRUE,
            1, 12, 3, 6,
            undef, undef, undef, undef,
            -1, 110);            # Fixed height

        # Categories
        $self->addLabel($table, '<b>Categories</b>',
            0, 12, 6, 7);

        $self->addLabel($table,
            '<i>List of applicable categories (\'weapon\', \'portable\' etc)</i>',
            1, 12, 7, 8);
        my $textView = $self->addTextView($table, 'categoryList', FALSE,
            1, 12, 8, 11,
            undef, undef, undef, undef,
            -1, 110);            # Fixed height

        @comboList = (
            'region', 'room',
            'weapon', 'armour', 'garment',
            'char', 'minion', 'sentient', 'creature',
            'portable', 'decoration', 'custom',
        );

        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,               # No 'undef' value used
            1, 4, 11, 12);

        my $button = $self->addButton($table, '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($table, '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 (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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
        my ($vBox, $table) = $self->addTab('_Adjectives', $self->notebook);

        # Adjectives
        $self->addLabel($table, '<b>Adjectives</b>',
            0, 6, 0, 2);

        $self->addLabel($table, '<i>List of normal adjectives</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'adjList', TRUE,
            1, 12, 4, 6);

        $self->addLabel($table, '<i>List of pseudo-adjectives</i>',
            1, 12, 6, 8);
        $self->addTextView($table, 'pseudoAdjList', TRUE,
            1, 12, 8, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        my ($vBox, $table) = $self->addTab('_Unknowns', $self->notebook);

        # Unknowns
        $self->addLabel($table, '<b>Unknowns</b>',
            0, 6, 0, 2);

        $self->addLabel($table, '<i>List of unrecognised words</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'unknownWordList', TRUE,
            1, 12, 4, 6);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 {}        # 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->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 object 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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Names
        $self->addLabel($table, '<b>Name</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Mission (if any)',
            1, 3, 2, 3);
        @comboList = sort {lc($a) cmp lc($b)} ($self->session->currentWorld->ivKeys('missionHash'));
        $self->addComboBox($table, 'missionName', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);

        # Points
        $self->addLabel($table, '<b>Points</b>',
            7, 12, 0, 1);
        $self->addLabel($table, 'Quest points',
            7, 9, 1, 2);
        $self->addEntryWithIcon($table, 'questPoints', 'float', undef, undef,
            9, 12, 1, 2);       # (Negative/fractional values allowed)
        $self->addLabel($table, 'Quest XP',
            7, 9, 2, 3);
        $self->addEntryWithIcon($table, 'questXP', 'float', undef, undef,
            9, 12, 2, 3);
        $self->addLabel($table, 'Quest cash',
            7, 9, 3, 4);
        $self->addEntryWithIcon($table, 'questCash', 'float', undef, undef,
            9, 12, 3, 4);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Solution', $self->notebook);

        # Quest solutions
        $self->addLabel($table, '<b>Quest solutions</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Add your notes about the quest solution here (use missions to automate quests)</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'solutionList', TRUE,
            1, 12, 4, 10,
            TRUE, FALSE, FALSE, FALSE,   # Treat as a list, don't remove empty lines or whitespace
            -1, 330);                    # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Comments', $self->notebook);

        # Quest comments
        $self->addLabel($table, '<b>Quest comments</b>',
            0, 12, 0, 2);
        $self->addLabel($table, '<i>Add your notes about the quest here</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'commentList', TRUE,
            1, 12, 4, 10,
            TRUE, FALSE, FALSE, FALSE,    # Treat as a list, don't remove empty lines or whitespace
            -1, 330);                     # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 {}        # 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->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 object viewer window, if it is open
            if ($self->session->viewerWin) {

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

            # Redraw this region (only) 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
        #   $table  -   The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # General properties
        $self->addLabel($table, '<b>General properties</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, 'Region name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'World model number',
            1, 3, 2, 3);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Current level',
            1, 3, 3, 4);
        $self->addEntry($table, 'currentLevel', FALSE,
            3, 6, 3, 4);
        $self->addLabel($table, 'Highest occupied level',
            1, 3, 4, 5);
        $self->addEntry($table, 'highestLevel', FALSE,
            3, 6, 4, 5);
        $self->addLabel($table, 'Lowest occupied level',
            1, 3, 5, 6);
        $self->addEntry($table, 'lowestLevel', FALSE,
            3, 6, 5, 6);

        # Right column
        $self->addLabel($table, 'Current magnification',
            7, 9, 1, 2);
        $self->addEntry($table, 'magnification', FALSE,
            9, 12, 1, 2);
        $self->addLabel($table, 'Map position X',
            7, 9, 2, 3);
        $self->addEntry($table, 'scrollXPos', FALSE,
            9, 12, 2, 3);
        $self->addLabel($table, 'Map position Y',
            7, 9, 3, 4);
        $self->addEntry($table, 'scrollYPos', FALSE,
            9, 12, 3, 4);
        $self->addLabel($table, 'Maximum zoom out X',
            7, 11, 4, 5);
        $self->addCheckButton($table, 'maxZoomOutXFlag', FALSE,
            11, 12, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Maximum zoom out Y',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'maxZoomOutYFlag', FALSE,
            11, 12, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Exit drawing mode',
            7, 9, 6, 7);
        $self->addEntry($table, 'drawExitMode', FALSE,
            9, 12, 6, 7);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Sizes', $self->notebook);

        # Import the session's GA::Obj::WorldModel
        $wmObj = $self->session->worldModelObj;

        # Default sizes
        $self->addLabel($table, '<b>Current sizes</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Size of the regionmap, in gridblocks (use odd numbers; minimum value is 5)</i>',
            1, 12, 1, 2,
        );

        $self->addLabel($table, 'Width (x axis)',
            1, 3, 2, 3);
        $self->addEntryWithIcon(
            $table,
            'gridWidthBlocks',
            'odd',
            5,
            $wmObj->maxGridWidthBlocks,
            3, 6, 2, 3,
        );

        $self->addLabel($table, 'Maximum width',
            7, 9, 2, 3);
        my $entry = $self->addEntry($table, undef, FALSE,
            9, 12, 2, 3);
        $entry->set_text($wmObj->maxGridWidthBlocks);

        $self->addLabel($table, 'Height (y axis)',
            1, 3, 3, 4);
        $self->addEntryWithIcon(
            $table,
            'gridHeightBlocks',
            'odd',
            5,
            $wmObj->maxGridHeightBlocks,
            3, 6, 3, 4,
        );

        $self->addLabel($table, 'Maximum height',
            7, 9, 3, 4);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            9, 12, 3, 4);
        $entry2->set_text($wmObj->maxGridHeightBlocks);

        $self->addLabel(
            $table,
            '<i>Size of a gridblock, in pixels (use odd numbers; minimum value is 11)</i>',
            1, 12, 4, 5,
        );

        $self->addLabel($table, 'Width',
            1, 3, 5, 6);
        $self->addEntryWithIcon(
            $table,
            'blockWidthPixels',
            'odd',
            11,
            $wmObj->maxBlockWidthPixels,
            3, 6, 5, 6,
        );

        $self->addLabel($table, 'Maximum width',
            7, 9, 5, 6);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            9, 12, 5, 6);
        $entry3->set_text($wmObj->maxBlockWidthPixels);

        $self->addLabel($table, 'Height',
            1, 3, 6, 7);
        $self->addEntryWithIcon(
            $table,
            'blockHeightPixels',
            'odd',
            11,
            $wmObj->maxBlockHeightPixels,
            3, 6, 6, 7,
        );

        $self->addLabel($table, 'Maximum height',
            7, 9, 6, 7);
        my $entry4 = $self->addEntry($table, undef, FALSE,
            9, 12, 6, 7);
        $entry4->set_text($wmObj->maxBlockHeightPixels);

        $self->addLabel(
            $table,
            '<i>Size of a room, in pixels (use odd numbers; minimum value is 5)</i>',
            1, 12, 7, 8,
        );

        $self->addLabel($table, 'Width',
            1, 3, 8, 9);
        $self->addEntryWithIcon(
            $table,
            'roomWidthPixels',
            'odd',
            5,
            $wmObj->maxRoomWidthPixels,
            3, 6, 8, 9,
        );

        $self->addLabel($table, 'Maximum width',
            7, 9, 8, 9);
        my $entry5 = $self->addEntry($table, undef, FALSE,
            9, 12, 8, 9);
        $entry5->set_text($wmObj->maxRoomWidthPixels);

        $self->addLabel($table, 'Height',
            1, 3, 9, 10);
        $self->addEntryWithIcon(
            $table,
            'roomHeightPixels',
            'odd',
            5,
            $wmObj->maxRoomHeightPixels,
            3, 6, 9, 10,
        );

        $self->addLabel($table, 'Maximum height',
            7, 8, 9, 10);
        my $entry6 = $self->addEntry($table, undef, FALSE,
            9, 12, 9, 10);
        $entry6->set_text($wmObj->maxRoomHeightPixels);

        $self->addLabel(
            $table,
            '<i>Current size of the regionmap, in pixels (use the \'save\' button to update these'
            . ' values)</i>',
            1, 12, 10, 11,
        );
        $self->addLabel($table, 'Width',
            1, 3, 11, 12);
        $self->addEntry($table, 'mapWidthPixels', FALSE,
            3, 6, 11, 12);
        $self->addLabel($table, 'Height',
            7, 9, 11, 12);
        $self->addEntry($table, 'mapHeightPixels', FALSE,
            9, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Objects', $self->notebook);

        # 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 Gtk2::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
        my ($vBox, $table) = $self->addTab('_Rooms', $innerNotebook);

        $self->addLabel($table, '<b>Rooms</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);

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

        # Add editing buttons
        my $button = $self->addButton($table, '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub objects1Tab_refreshList {

        # Called by $self->objects1Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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
        my ($vBox, $table) = $self->addTab('Room _tags', $innerNotebook);

        $self->addLabel($table, '<b>Room tags</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub objects2Tab_refreshList {

        # Called by $self->objects2Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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
        my ($vBox, $table) = $self->addTab('Room _guilds', $innerNotebook);

        $self->addLabel($table, '<b>Room guilds</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub objects3Tab_refreshList {

        # Called by $self->objects3Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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
        my ($vBox, $table) = $self->addTab('_Exits', $innerNotebook);

        $self->addLabel($table, '<b>Exits</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);

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

        # Add editing buttons
        my $button = $self->addButton($table, '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub objects4Tab_refreshList {

        # Called by $self->objects4Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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
        my ($vBox, $table) = $self->addTab('Exit t_ags', $innerNotebook);

        $self->addLabel($table, '<b>Exit tags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            1, 270);        # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub objects5Tab_refreshList {

        # Called by $self->objects5Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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
        my ($vBox, $table) = $self->addTab('_Labels', $innerNotebook);

        $self->addLabel($table, '<b>Labels</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub objects6Tab_refreshList {

        # Called by $self->objects6Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 ($vBox, $table) = $self->addTab('_Counts', $self->notebook);

        # Default sizes
        $self->addLabel($table, '<b>Living/non-living counts</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub countsTab_refreshList {

        # Called by $self->countsTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Region paths', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        $self->addLabel($table, '<b>Region exits</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);       # Fixed height

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

        # Add editing buttons
        $self->boundaries1Tab_addButtons($table, $slWidget, scalar (@columnList / 2));

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub boundaries1Tab_addButtons {

        # Called by $self->boundaries1Tab and ->boundaries2Tab to add editing buttons below the
        #   GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $table          - The Gtk2::Table
        #   $slWidget       - The GA::Gtk::Simple::List
        #   $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, $table, $slWidget, $columns, $superFlag, $check) = @_;

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

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

        my $button = $self->addButton(
            $table, '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(
            $table,
            'Recalculate region paths for exit',
            'Recalculate region paths to/from the selected exit',
            undef,
            3, 6, 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(
            $table,
            'Refresh list',
            'Refresh the list of exit model objects',
            undef,
            10, 12, 10, 11,
        );
        $button3->signal_connect('clicked' => sub {

            # Refresh the simple list
            $self->boundaries1Tab_refreshList($slWidget, $columns, $superFlag);
        });

        my $button4 = $self->addButton(
            $table,
            'Mark as super-region exit',
            'Make the selected exit a 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 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 $button5 = $self->addButton(
            $table,
            'Mark as exclusive super-region exit',
            'Make the selected exit an exclusive super-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);

                # 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 $button6 = $self->addButton(
            $table,
            'Mark as normal region exit',
            'Make the selected exit a normal region exit',
            undef,
            8, 12, 11, 12,
            TRUE);              # Irreversible
        $button6->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);
            }
        });

        return 1;
    }

    sub boundaries1Tab_refreshList {

        # Called by $self->boundaries1Tab, >boundaries2Tab and ->boundaries1Tab_addButtons refresh
        #   the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        $self->addLabel($table, '<b>Super-region exits</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);       # Fixed height

        # 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($table, $slWidget, scalar (@columnList / 2), TRUE);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub boundaries3Tab {

        # Boundaries3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        $self->addLabel($table, '<b>Region paths</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table, '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub boundaries3Tab_refreshList {

        # Called by both $self->boundaries3Tab and $self->boundaries4Tab to refresh their
        #   GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        $self->addLabel($table, '<b>Region safe paths</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        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::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
#       my ($vBox, $table) = $self->addTab('_Route', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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) = @_;

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

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

        # Tab setup
        my ($vBox, $table) = $self->addTab('_Route', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>General properties</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Route type',
            1, 3, 1, 2);
        $self->addEntry($table, 'routeType', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Start room tag',
            1, 3, 2, 3);
        $self->addEntry($table, 'startRoom', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Stop room tag',
            1, 3, 3, 4);
        $self->addEntry($table, 'startRoom', FALSE,
            3, 6, 3, 4);
        $self->addLabel($table, 'Circuit name',
            1, 3, 4, 5);
        $self->addEntry($table, 'circuitName', FALSE,
            3, 6, 4, 5);

        $self->addLabel($table, '<b>Route</b>',
            0, 12, 5, 6);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            1, 12, 6, 7);
        # (->signal_connect appears below)
        $entry->set_text($self->editObj->route);

        $self->addLabel($table, 'Number of steps',
            1, 3, 7, 8);
        my $entry2 = $self->addEntry($table, 'stepCount', FALSE,
            3, 6, 7, 8);

        # Right column
        $self->addLabel($table, 'Hoppable',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'hopFlag', TRUE,
            11, 12, 1, 2);

        $entry->signal_connect('changed' => sub {

            my $text = $entry->get_text();

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

                # Set the IV directly, rather than using ->editHash
                $self->editObj->ivPoke('route', $text);
                # ...so that ->stepCount can also be updated
                $self->editObj->resetStepCount();
                $entry2->set_text($self->editObj->stepCount);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Strip object settings</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Number',
            1, 3, 1, 2);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Type',
            1, 3, 2, 3);
        $self->addEntry($table, 'type', FALSE,
            3, 6, 2, 3);

        $self->addLabel($table, 'Used in window',
            1, 3, 3, 4);
        my $entry = $self->addEntry($table, undef, FALSE,
            3, 6, 3, 4);
        $entry->set_text($self->editObj->winObj->number);

        $self->addLabel($table, 'Visible',
            1, 5, 4, 5);
        my $checkButton = $self->addCheckButton($table, 'visibleFlag', FALSE,
            5, 6, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Jealous',
            1, 5, 5, 6);
        my $checkButton2 = $self->addCheckButton($table, 'jealousyFlag', FALSE,
            5, 6, 5, 6, 1, 0.5);

        # Right column
        $self->addLabel($table, 'Expand (use share of empty space)',
            7, 11, 1, 2);
        my $checkButton3 = $self->addCheckButton($table, 'expandFlag', FALSE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Fill (empty space used, not as padding)',
            7, 11, 2, 3);
        my $checkButton4 = $self->addCheckButton($table, 'fillFlag', FALSE,
            11, 12, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Spacing (use gap between adjacent strips)',
            7, 11, 3, 4);
        my $checkButton5 = $self->addCheckButton($table, 'spacingFlag', FALSE,
            11, 12, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Strip available in blind mode',
            7, 11, 4, 5);
        my $checkButton6 = $self->addCheckButton($table, 'blindFlag', FALSE,
            11, 12, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Strip can receive focus',
            7, 11, 5, 6);
        my $checkButton7 = $self->addCheckButton($table, 'allowFocusFlag', FALSE,
            11, 12, 5, 6, 1, 0.5);

        # Add a button
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Settings', $self->notebook);

        # Initial settings
        $self->addLabel($table, '<b>Initial settings</b>',
            0, 12, 0, 1);
            # Bottom section
        $self->addLabel($table, '<i>Strip object\'s customisation settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($table, 'initHash', \@columnList,
            1, 12, 2, 12,
            -1, 330);     # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Table object settings</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Number',
            1, 3, 1, 2);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Type',
            1, 3, 2, 3);
        $self->addEntry($table, 'type', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Optional name',
            1, 3, 3, 4);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 3, 4);
        $self->addLabel($table, 'Can be removed from table',
            1, 5, 4, 5);
        my $checkButton = $self->addCheckButton($table, 'allowRemoveFlag', FALSE,
            5, 6, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Can be resized on table',
            1, 5, 5, 6);
        my $checkButton2 = $self->addCheckButton($table, 'allowResizeFlag', FALSE,
            5, 6, 5, 6, 1, 0.5);

        # Right column
        $self->addLabel($table, 'Tablezone number',
            7, 9, 1, 2);
        my $entry = $self->addEntry($table, undef, FALSE,
            9, 12, 1, 2);
        $entry->set_text($self->editObj->zoneObj->number);

        $self->addLabel($table, 'Top-left X',
            7, 9, 2, 3);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            9, 12, 2, 3);
        $entry2->set_text($self->editObj->zoneObj->left);

        $self->addLabel($table, 'Top-left Y',
            7, 9, 3, 4);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            9, 12, 3, 4);
        $entry3->set_text($self->editObj->zoneObj->top);

        $self->addLabel($table, 'Bottom-right X',
            7, 9, 4, 5);
        my $entry4 = $self->addEntry($table, undef, FALSE,
            9, 12, 4, 5);
        $entry4->set_text($self->editObj->zoneObj->right);

        $self->addLabel($table, 'Bottom-right Y',
            7, 9, 5, 6);
        my $entry5 = $self->addEntry($table, undef, FALSE,
            9, 12, 5, 6);
        $entry5->set_text($self->editObj->zoneObj->bottom);

        # Add a button
        my $button = $self->addButton($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Settings', $self->notebook);

        # Initial settings
        $self->addLabel($table, '<b>Initial settings</b>',
            0, 12, 0, 1);
            # Bottom section
        $self->addLabel($table, '<i>Table object\'s customisation settings</i>',
            1, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($table, 'initHash', \@columnList,
            1, 12, 2, 12,
            -1, 330);     # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 {}        # Inherited from GA::Generic::ConfigWin

    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 object 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
        #   $table  - The Gtk2::Table
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $cmdObj, $packageName, $prettyName,
            @list,
        );

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Names
        $self->addLabel($table, '<b>Names</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Capitalised name',
            1, 3, 2, 3);
        $self->addEntry($table, 'prettyName', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Unique name',
            1, 3, 3, 4);
        $self->addEntry($table, 'uniqueName', FALSE,
            3, 6, 3, 4);

        $self->addLabel($table, 'Short name',
            7, 9, 1, 2);
        $self->addEntry($table, 'shortName', FALSE,
            9, 12, 1, 2);
        $self->addLabel($table, 'Custom task name',
            7, 9, 2, 3);
        if ($self->editObj->taskType && $self->editObj->taskType eq 'custom') {

            $self->addEntry($table, 'customName', FALSE,
                9, 12, 2, 3);

        } else {

            $self->addLabel($table, '<i>n/a</i>',
                9, 12, 2, 3);
        }

        # Description
        $self->addLabel($table, '<b>Description and task help</b>',
            0, 12, 4, 5);
        $self->addLabel($table, '<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($table, undef, FALSE,
            1, 12, 6, 12,
            undef, undef, undef, undef,
            -1, 230);   # Fixed height
        my $buffer = $textView->get_buffer();
        $buffer->set_text(join("\n", @list));

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Settings', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Flag set to FALSE for current tasks, TRUE otherwise
        $editFlag = $self->ivShow('editConfigHash', 'edit_flag');

        # General settings
        $self->addLabel($table, '<b>General settings</b>',
            0, 6, 0, 1);

        $self->addLabel($table, 'Category',
            1, 3, 1, 2);
        $self->addEntry($table, 'category', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Tasklist type',
            1, 3, 2, 3);
        $self->addEntry($table, 'taskType', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Associated profile',
            1, 3, 3, 4);
        $self->addEntry($table, 'profName', FALSE,
            3, 6, 3, 4);
        $self->addLabel($table, 'Profile category',
            1, 3, 4, 5);
        $self->addEntry($table, 'profCategory', FALSE,
            3, 6, 4, 5);
        $self->addLabel($table, 'Shortcut IV',
            1, 3, 5, 6);
        $self->addEntry($table, 'shortCutIV', FALSE,
            3, 6, 5, 6);

        # Status settings
        $self->addLabel($table, '<b>Status settings</b>',
            0, 6, 6, 7);

        $self->addLabel($table, '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($table, 'status', \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 7, 8);

        } else {

            $self->addEntry($table, 'status', FALSE,
                3, 6, 7, 8);
        }

        $self->addLabel($table, 'Resume status',
            1, 3, 8, 9);
        $self->addEntry($table, 'resumeStatus', FALSE,
            3, 6, 8, 9);
        $self->addLabel($table, 'Task stage',
            1, 3, 9, 10);
        $self->addEntry($table, 'stage', FALSE,
            3, 6, 9, 10);

        # Start/stop settings
        $self->addLabel($table, '<b>Start/stop settings</b>',
            7, 13, 0, 1);
        $self->addLabel($table, 'Start time',
            8, 11, 1, 2);
        $self->addEntry($table, 'startTime', $editFlag,
            11, 13, 1, 2);
        $self->addLabel($table, 'Next check time',
            8, 11, 2, 3);
        $self->addEntry($table, 'checkTime', $editFlag,
            11, 13, 2, 3);
        $self->addLabel($table, 'End status',
            8, 11, 3, 4);
        if ($editFlag) {

            # These are all the acceptable values for ->endStatus
            @comboList2 = (
                'unlimited',
                'run_for',
                'run_until',
            );

            $self->addComboBox($table, 'endStatus', \@comboList2, '',
                TRUE,               # No 'undef' value used
                11, 13, 3, 4);

        } else {

            $self->addEntry($table, 'endStatus', FALSE,
                11, 13, 3, 4);
        }

        $self->addLabel($table, 'End time',
            8, 11, 4, 5);
        $self->addEntry($table, 'endTime', $editFlag,
            11, 13, 4, 5);
        $self->addLabel($table, 'Dependent task',
            8, 11, 5, 6);
        $self->addEntry($table, 'waitForTask', $editFlag,
            11, 13, 5, 6);

        # Task loop settings
        $self->addLabel($table, '<b>Task loop settings</b>',
            7, 12, 6, 7);
        $self->addLabel($table, 'Call interval (secs)',
            8, 11, 7, 8);
        $self->addEntry($table, 'delayTime', FALSE,
            11, 13, 7, 8);

        # Text-to-speech (TTS) settings
        $self->addLabel($table, '<b>Text-to-speech settings</b> <i>(see also pages 3-5)</i>',
            7, 13, 8, 9);
        $self->addLabel($table, '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($table, 'ttsConfig', \@comboList3, '',
                FALSE,               # 'undef' value used
                11, 13, 9, 10);

        } else {

            # ...otherwise, use a normal entry
            $self->addEntry($table, 'ttsConfig', FALSE,
                11, 13, 9, 10);
        }

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Flag set to FALSE for current tasks, TRUE otherwise
        $editFlag = $self->ivShow('editConfigHash', 'edit_flag');

        # Flags
        $self->addLabel($table, '<b>Flags</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Jealous (only 1 copy can run)',
            1, 5, 1, 2);
        $self->addCheckButton($table, 'jealousyFlag', FALSE,
            5, 6, 1, 2);
        $self->addLabel($table, 'Can be used as initial/custom task)',
            1, 5, 2, 3);
        $self->addCheckButton($table, 'storableFlag', FALSE,
            5, 6, 2, 3);
        $self->addLabel($table, 'Requires Locator task',
            1, 5, 3, 4);
        $self->addCheckButton($table, 'requireLocatorFlag', FALSE,
            5, 6, 3, 4);
        $self->addLabel($table, 'Resets when profiles change',
            1, 5, 4, 5);
        $self->addCheckButton($table, 'profSensitivityFlag', FALSE,
            5, 6, 4, 5);
        $self->addLabel($table, 'Active mode on',
            1, 5, 5, 6);
        $self->addCheckButton($table, 'activeFlag', FALSE,
            5, 6, 5, 6);
        $self->addLabel($table, 'Due to shut down (gracefully)',
            1, 5, 6, 7);
        $self->addCheckButton($table, 'shutdownFlag', FALSE,
            5, 6, 6, 7);

        # Window settings - startup
        $self->addLabel($table, '<b>Window settings - startup</b>',
            0, 6, 7, 8);
        $self->addLabel($table, 'Task window is allowed',
            1, 5, 8, 9);
        $self->addCheckButton($table, 'allowWinFlag', FALSE,
            5, 6, 8, 9);
        $self->addLabel($table, 'Task window is required',
            1, 5, 9, 10);
        $self->addCheckButton($table, 'requireWinFlag', FALSE,
            5, 6, 9, 10);
        $self->addLabel($table, 'Window opens when task starts',
            1, 5, 10, 11);
        my $button = $self->addCheckButton($table, 'startWithWinFlag', FALSE,
            5, 6, 10, 11);
        if ($self->editObj->allowWinFlag && ! $self->editObj->requireWinFlag) {

            $button->set_sensitive(TRUE);
        }

        # Window settings - general
        $self->addLabel($table, '<b>Window settings - general</b>',
            7, 12, 0, 1);
        $self->addLabel($table, 'Preferred location(s)',
            8, 10, 1, 2);
        my $entry = $self->addEntry($table, undef, FALSE,
            10, 12, 1, 2);
        $entry->set_text(join(', ', $self->editObj->winPreferList));

        $self->addLabel($table, 'Task window currently open',
            8, 10, 2, 3);
        my $entry2 = $self->addEntry($table, 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->addLabel($table, 'Task window uses an entry box',
            8, 11, 3, 4);
        $self->addCheckButton($table, 'taskWinEntryFlag', FALSE,
            11, 12, 3, 4);
        $self->addLabel($table, 'Winmap used by window',
            8, 10, 4, 5);
        $self->addEntry($table, 'winmap', FALSE,
            10, 12, 4, 5);
        $self->addLabel($table, 'Function used to set up the window',
            8, 10, 5, 6);
        $self->addEntry($table, 'winUpdateFunc', FALSE,
            10, 12, 5, 6);
        $self->addLabel($table, 'Tab mode',
            8, 10, 6, 7);
        $self->addEntry($table, 'tabMode', FALSE,
            10, 12, 6, 7);
        $self->addLabel($table, 'Window does not scroll to bottom',
            8, 10, 7, 8);
        $self->addCheckButton($table, 'noScrollFlag', FALSE,
            11, 12, 7, 8);

        # Window settings - colour
        $self->addLabel($table, '<b>Window settings - colour</b>',
            7, 12, 8, 9);
        $self->addLabel($table, 'Preferred colour scheme',
            8, 10, 9, 10);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('colourSchemeHash'));
        $self->addComboBox($table, 'colourScheme', \@comboList, '',
            FALSE,               # 'undef' value used
            10, 12, 9, 10);
        $self->addLabel($table, 'Window is monochrome',
            8, 11, 10, 11);
        $self->addCheckButton($table, 'monochromeFlag', FALSE,
            11, 12, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings3Tab {

        # Settings3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Text-to-speech attributes
        $self->addLabel($table, '<b>Text-to-speech attributes</b>',
            0, 13, 0, 1);

        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 13, 2, 10,
            -1, 270);      # Fixed size

        # 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($table, 'Attribute',
                1, 3, 11, 12);
            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('ttsAttribHash'));
            my $combo = $self->addComboBox($table, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 5, 11, 12);

            $self->addLabel($table, 'Optional value',
                5, 7, 11, 12);
            my $entry = $self->addEntry($table, undef, TRUE,
                7, 9, 11, 12);

            my $button = $self->addButton(
                $table,
                ';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(
                $table,
                ';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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings3Tab_refreshList {

        # Called by $self->settings3Tab, ->settings4Tab and ->settings5 Tab to refresh the
        #   GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Text-to-speech flag attributes
        $self->addLabel($table, '<b>Text-to-speech flag attributes</b>',
            0, 13, 0, 1);

        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 13, 2, 10,
            -1, 270);      # Fixed size

        # 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($table, 'Flag attribute',
                1, 3, 11, 12);
            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('ttsFlagAttribHash'));
            my $combo = $self->addComboBox($table, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 5, 11, 12);

            my $button = $self->addButton(
                $table,
                ';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(
                $table,
                ';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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings5Tab {

        # Settings5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Text-to-speech alert attributes
        $self->addLabel($table, '<b>Text-to-speech alert attributes</b>',
            0, 13, 0, 1);

        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 13, 2, 10,
            -1, 270);      # Fixed size

        # 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($table, 'Alert attribute',
                1, 3, 11, 12);
            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('ttsAlertAttribHash'));
            my $combo = $self->addComboBox($table, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 5, 11, 12);

            $self->addLabel($table, 'Optional value',
                5, 7, 11, 12);
            my $entry = $self->addEntry($table, undef, TRUE,
                7, 9, 11, 12);

            my $button = $self->addButton(
                $table,
                ';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(
                $table,
                ';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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Parameters', $self->notebook);

        # 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 '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 'status_task') {

            $self->parametersStatus1Tab($innerNotebook);
            $self->parametersStatus2Tab($innerNotebook);
            $self->parametersStatus3Tab($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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Status task actions
        $self->addLabel($table, '<b>' . $self->editObj->prettyName . ' task parameters</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>This task has no user-configurable parameters</i>',
            1, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersAttack1Tab {

        # Parameters Attack1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Door patterns
        $self->addLabel($table, '<b>Fight success commands</b>',
            0, 12, 0, 2);
        $self->addLabel(
            $table, '<i>List of commands sent when the character wins a fight (not'
            . ' interpolated)</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'fightCmdList', TRUE,
            1, 12, 4, 6);

        # Locked door patterns
        $self->addLabel($table, '<b>Interaction success commands</b>',
            0, 12, 6, 8);
        $self->addLabel(
            $table, '<i>List of commands sent when the character wins an interaction (not'
            . ' interpolated)</i>',
            1, 12, 8, 10);
        $self->addTextView($table, 'interactCmdList', TRUE,
            1, 12, 10, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersAttack2Tab {

        # Parameters Attack2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Announcements
        $self->addLabel($table, '<b>Announcements</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Announce kills/completed interactions in \'main\' window',
            1, 11, 1, 2);
        $self->addCheckButton($table, 'announceFlag', TRUE,
            11, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersChannels1Tab {

        # Parameters Channels1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Summary tab
        $self->addLabel($table, '<b>Summary tab</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'single\'', 'summaryMode',
            'single',   # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 1, 2);
        $self->addLabel($table, 'Use a single tab, in which text for all channels is displayed',
            3, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'multi\'', 'summaryMode', 'multi', TRUE,
            1, 3, 2, 3);
        $self->addLabel($table,
            '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(
            $table, $group, '\'default\'', 'summaryMode', 'default', TRUE,
            1, 3, 3, 4);
        $self->addLabel($table,
            'Every channel has its own tab and there is no \'summary\' tab',
            3, 12, 3, 4);

        $self->addLabel($table,
            'Name of the \'summary\' tab, when used (1-16 characters, including spaces)',
            1, 8, 4, 5);
        $self->addEntryWithIcon($table, 'summaryChannel', 'string', 1, 16,
            8, 12, 4, 5,
            16, 16);

        # Display options
        $self->addLabel($table, '<b>Display options</b>',
            0, 12, 5, 6);

        my $checkButton = $self->addCheckButton($table, 'tabCloseButtonFlag', TRUE,
            1, 12, 6, 7);
        $checkButton->set_label('Tabs in the task window have a close button');
        my $checkButton2 = $self->addCheckButton($table, 'capitaliseFlag', TRUE,
            1, 12, 7, 8);
        $checkButton2->set_label('Channels names should be capitalised');
        my $checkButton3 = $self->addCheckButton($table, 'useColourStyleFlag', TRUE,
            1, 12, 8, 9);
        $checkButton3->set_label(
            'The line\'s original colour/style tags are presereved in the task window',
        );

        $self->addLabel($table,
            '<i>NB Changes to the values on this page are applied when task is restarted/reset</i>',
            1, 12, 9, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersChannels2Tab {

        # Parameters Channels2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Initial channel list
        $self->addLabel($table, '<b>Initial channel list</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>Lists of channels for which a tab is automatically created when the task window'
            . ' opens</i>',
            1, 12, 2, 4);
        $self->addTextView($table, 'initChannelList', TRUE,
            1, 12, 4, 6,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
            -1, 130);                # Fixed height

        # Ignore channel list
        $self->addLabel($table, '<b>Ignore channel list</b>',
            0, 12, 6, 8);
        $self->addLabel($table,
            '<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($table, 'ignoreChannelList', TRUE,
            1, 12, 10, 12,
            TRUE, TRUE, TRUE, FALSE, # Treat as list, remove empty lines, remove whitespace
            -1, 130);                # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersChannels3Tab {

        # Parameters Channels3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Sound effects
        $self->addLabel($table, '<b>Sound effects</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>List of sound effects that should be played when text is redirected into a'
            . ' channel</i>',
            1, 12, 2, 4);

        # Add a simple list
        @columnList = (
            'Channel', 'text',
            'Sound effect', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);      # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Channel',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, 16,
            3, 6, 10, 11,
            16, 16);

        $self->addLabel($table, 'Sound effect (leave empty to play nothing)',
            6, 9, 10, 11);
        my $entry2 = $self->addEntry($table, undef, TRUE,
            9, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersChannels4Tab {

        # Parameters Channels4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Urgency hints
        $self->addLabel($table, '<b>Urgency hints</b>',
            0, 12, 0, 2);
        $self->addLabel($table,
            '<i>List of channels for which the task window\'s urgency hint should be set (to make'
            . ' the window flash)</i>',
            1, 12, 2, 4);

        # Add a simple list
        @columnList = (
            'Channel', 'text',
            'Urgency hint', 'bool',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);      # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Channel',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, 16,
            3, 6, 10, 11,
            16, 16);

        $self->addLabel($table, 'Use urgency hint when text redirected',
            6, 11, 10, 11);
        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            11, 12, 10, 11);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersCompass1Tab {

        # Parameters Compass1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Keypad commands
        $self->addLabel($table, '<b>Keypad commands</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of customisable keypad commands</i>',
            1, 6, 1, 2);

        $self->addLabel($table,
            'Compass task is (or starts) enabled',
            7, 11, 1, 2);
        my $checkButton = $self->addCheckButton($table, 'enabledFlag', FALSE,
            11, 12, 1, 2);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Command', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 3, 10,
            -1, 220);      # Fixed height

        # 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($table, 'Key:',
                1, 3, 10, 11);

            @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('keypadCmdHash'));
            my $combo = $self->addComboBox($table, undef, \@comboList, '',
                TRUE,              # no 'undef' value allowed
                3, 6, 10, 11);

            $self->addLabel($table, 'Command:',
                6, 8, 10, 11);
            my $entry = $self->addEntry($table, undef, TRUE,
                8, 12, 10, 11);

            my $button = $self->addButton(
                $table, '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(
                $table, 'Reset list', 'Reset the list of keypad world commands', undef,
                10, 12, 11, 12);

        } else {

            $button2 = $self->addButton(
                $table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersCondition1Tab {

        # Parameters Condition1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Object sensitivity
        $self->addLabel($table, '<b>Object sensitivity</b>',
            0, 12, 0, 1);

        $self->addLabel(
            $table, 'How closely model objects much match, when compared (0-100)',
            1, 9, 1, 2);
        $self->addEntryWithIcon($table, 'sensitivity', 'int', 0, 100,
            9, 12, 1, 2);

        $self->addLabel(
            $table, '<i>0 - any objects match, 100 - only identical objects match,</i>',
            2, 12, 2, 3);
        $self->addLabel(
            $table, '<i>70 - objects must be fairly similar to match</i>',
            2, 12, 3, 4);
        $self->addLabel(
            $table, '<i>90 - objects must be very similar to match</i>',
            2, 12, 4, 5);

        # Check parameters
        $self->addLabel($table, '<b>Check parameters</b>',
            0, 12, 5, 6);

        $self->addLabel($table, 'Condition check performed only once',
            1, 11, 6, 7);
        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            11, 12, 6, 7);

        $self->addLabel($table, 'Seconds to wait before first check',
            1, 9, 7, 8);
        my $entry = $self->addEntryWithIcon($table, 'firstCheckTime', 'int', 0, undef,
            9, 12, 7, 8);
        $self->addLabel($table, 'Seconds to wait between checks',
            1, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, 'waitTime', 'int', 1, undef,
            9, 12, 8, 9);
        $self->addLabel($table, 'Max seconds to wait while checking each object',
            1, 9, 9, 10);
        my $entry3 = $self->addEntryWithIcon($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersSystem1Tab {

        # Parameters System1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # System message display modes
        $self->addLabel($table, '<b>System message display modes</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, '<i>Ordinary system messages</i>',
            1, 6, 1, 2);

        my ($group, $group2, $group3, $group4, $group5, $radioButton);

        ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'main\'', 'systemMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 2, 3);
        $self->addLabel($table, 'Displayed in the \'main\' window only',
            3, 6, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'both\'', 'systemMode', 'both', TRUE,
            1, 3, 3, 4);
        $self->addLabel($table, 'Displayed in \'main\' and task windows',
            3, 6, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'task\'', 'systemMode', 'task', TRUE,
            1, 3, 4, 5);
        $self->addLabel($table, 'Displayed in the task window only',
            3, 6, 4, 5);

        $self->addLabel($table, '<i>Error messages</i>',
            1, 6, 5, 6);

        ($group2, $radioButton) = $self->addRadioButton(
            $table, undef, '\'main\'', 'errorMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 6, 7);
        $self->addLabel($table, 'Displayed in the \'main\' window only',
            3, 6, 6, 7);

        ($group2, $radioButton) = $self->addRadioButton(
            $table, $group2, '\'both\'', 'errorMode', 'both', TRUE,
            1, 3, 7, 8);
        $self->addLabel($table, 'Displayed in \'main\' and task windows',
            3, 6, 7, 8);

        ($group2, $radioButton) = $self->addRadioButton(
            $table, $group2, '\'task\'', 'errorMode', 'task', TRUE,
            1, 3, 8, 9);
        $self->addLabel($table, 'Displayed in the task window only',
            3, 6, 8, 9);

        $self->addLabel($table, '<i>Warning messages</i>',
            1, 6, 9, 10);

        ($group3, $radioButton) = $self->addRadioButton(
            $table, undef, '\'main\'', 'warningMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 10, 11);
        $self->addLabel($table, 'Displayed in the \'main\' window only',
            3, 6, 10, 11);

        ($group3, $radioButton) = $self->addRadioButton(
            $table, $group3, '\'both\'', 'warningMode', 'both', TRUE,
            1, 3, 11, 12);
        $self->addLabel($table, 'Displayed in \'main\' and task windows',
            3, 6, 11, 12);

        ($group3, $radioButton) = $self->addRadioButton(
            $table, $group3, '\'task\'', 'warningMode', 'task', TRUE,
            1, 3, 12, 13);
        $self->addLabel($table, 'Displayed in the task window only',
            3, 6, 12, 13);

        # Right column
        $self->addLabel($table, '<i>Debug messages</i>',
            7, 12, 5, 6);

        ($group4, $radioButton) = $self->addRadioButton(
            $table, undef, '\'main\'', 'debugMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            7, 9, 6, 7);
        $self->addLabel($table, 'Displayed in the \'main\' window only',
            9, 12, 6, 7);

        ($group4, $radioButton) = $self->addRadioButton(
            $table, $group4, '\'both\'', 'debugMode', 'both', TRUE,
            7, 9, 7, 8);
        $self->addLabel($table, 'Displayed in \'main\' and task windows',
            9, 12, 7, 8);

        ($group4, $radioButton) = $self->addRadioButton(
            $table, $group4, '\'task\'', 'debugMode', 'task', TRUE,
            7, 9, 8, 9);
        $self->addLabel($table, 'Displayed in the task window only',
            9, 12, 8, 9);

        $self->addLabel($table, '<i>Improper arguments messages</i>',
            7, 12, 9, 10);

        ($group5, $radioButton) = $self->addRadioButton(
            $table, undef, '\'main\'', 'improperMode',
            'main',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            7, 9, 10, 11);
        $self->addLabel($table, 'Displayed in the \'main\' window only',
            9, 12, 10, 11);

        ($group5, $radioButton) = $self->addRadioButton(
            $table, $group5, '\'both\'', 'improperMode', 'both', TRUE,
            7, 9, 11, 12);
        $self->addLabel($table, 'Displayed in \'main\' and task windows',
            9, 12, 11, 12);

        ($group5, $radioButton) = $self->addRadioButton(
            $table, $group5, '\'task\'', 'improperMode', 'task', TRUE,
            7, 9, 12, 13);
        $self->addLabel($table, 'Displayed in the task window only',
            9, 12, 12, 13);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersSystem2Tab {

        # Parameters System2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # System message colours
        $self->addLabel($table, '<b>System message colours</b>',
            0, 12, 0, 1);

        $self->addCheckButton($table, 'colourFlag', TRUE,
            0, 1, 1, 2,
            0, 0.5);
        $self->addLabel($table, 'Preserve colour in task window',
            1, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersDivert1Tab {

        # Parameters Divert1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # 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($table, '<b>Background colour parameters (1/2)</b>',
            0, 13, 0, 1);

        $self->addLabel($table, 'Default colour',
            1, 3, 1, 2);
        $self->addComboBox($table, 'defaultColour', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 5, 1, 2);
        $self->addLabel($table, '<i>(Leave empty to use the normal background colour)</i>',
            5, 12, 1, 2);

        $self->addLabel($table, '<u>\'tell\'</u> channel colour',
            1, 3, 2, 3);
        $self->addComboBox($table, 'tellAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 2, 3);
        $self->addLabel($table, 'Interval (seconds)',
            5, 7, 2, 3);
        $self->addEntryWithIcon($table, 'tellAlertInterval', 'int', 1, undef,
            7, 9, 2, 3,
            8, 8);      # Max width
        $self->addLabel($table, 'Sound',
            9, 11, 2, 3);
        $self->addComboBox($table, 'tellAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 2, 3);

        $self->addLabel($table, 'Char limit <i>(0 - whole line)</i>',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'tellCharLimit', 'int', 0, undef,
            3, 5, 3, 4,
            8, 8);              # Max width
        $self->addLabel($table, 'Show room number',
            5, 8, 3, 4);
        $self->addCheckButton($table, 'tellRoomFlag', TRUE,
            8, 9, 3, 4,
            1, 0.5);
        $self->addLabel($table, 'Show urgency hint',
            9, 12, 3, 4);
        $self->addCheckButton($table, 'tellUrgencyFlag', TRUE,
            12, 13, 3, 4,
            1, 0.5);

        $self->addLabel($table, '<u>\'social\' channel colour</u>',
            1, 3, 4, 5);
        $self->addComboBox($table, 'socialAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 4, 5);
        $self->addLabel($table, 'Interval (seconds)',
            5, 7, 4, 5);
        $self->addEntryWithIcon($table, 'socialAlertInterval', 'int', 1, undef,
            7, 9, 4, 5,
            8, 8);              # Max width
        $self->addLabel($table, 'Sound',
            9, 11, 4, 5);
        $self->addComboBox($table, 'socialAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 4, 5);

        $self->addLabel($table, 'Character limit',
            1, 3, 5, 6);
        $self->addEntryWithIcon($table, 'socialCharLimit', 'int', 0, undef,
            3, 5, 5, 6,
            8, 8);              # Max width
        $self->addLabel($table, 'Show room number',
            5, 8, 5, 6);
        $self->addCheckButton($table, 'socialRoomFlag', TRUE,
            8, 9, 5, 6,
            1, 0.5);
        $self->addLabel($table, 'Show urgency hint',
            9, 12, 5, 6);
        $self->addCheckButton($table, 'socialUrgencyFlag', TRUE,
            12, 13, 5, 6,
            1, 0.5);

        $self->addLabel($table, '<u>\'custom\' channel colour</u>',
            1, 3, 6, 7);
        $self->addComboBox($table, 'customAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 6, 7);
        $self->addLabel($table, 'Interval (seconds)',
            5, 7, 6, 7);
        $self->addEntryWithIcon($table, 'customAlertInterval', 'int', 1, undef,
            7, 9, 6, 7,
            8, 8);              # Max width
        $self->addLabel($table, 'Sound',
            9, 11, 6, 7);
        $self->addComboBox($table, 'customAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 6, 7);

        $self->addLabel($table, 'Character limit',
            1, 3, 7, 8);
        $self->addEntryWithIcon($table, 'customCharLimit', 'int', 0, undef,
            3, 5, 7, 8,
            8, 8);              # Max width
        $self->addLabel($table, 'Show room number',
            5, 8, 7, 8);
        $self->addCheckButton($table, 'customRoomFlag', TRUE,
            8, 9, 7, 8,
            1, 0.5);
        $self->addLabel($table, 'Show urgency hint',
            9, 12, 7, 8);
        $self->addCheckButton($table, 'customUrgencyFlag', TRUE,
            12, 13, 7, 8,
            1, 0.5);

        $self->addLabel($table, '<u>\'warning\' channel colour</u>',
            1, 3, 8, 9);
        $self->addComboBox($table, 'warningAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 8, 9);
        $self->addLabel($table, 'Interval (seconds)',
            5, 7, 8, 9);
        $self->addEntryWithIcon($table, 'warningAlertInterval', 'int', 1, undef,
            7, 9, 8, 9,
            8, 8);              # Max width
        $self->addLabel($table, 'Sound',
            9, 11, 8, 9);
        $self->addComboBox($table, 'warningAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 8, 9);

        $self->addLabel($table, 'Character limit',
            1, 3, 9, 10);
        $self->addEntryWithIcon($table, 'warningCharLimit', 'int', 0, undef,
            3, 5, 9, 10,
            8, 8);              # Max width
        $self->addLabel($table, 'Show room number',
            5, 8, 9, 10);
        $self->addCheckButton($table, 'warningRoomFlag', TRUE,
            8, 9, 9, 10,
            1, 0.5);
        $self->addLabel($table, 'Show urgency hint',
            9, 12, 9, 10);
        $self->addCheckButton($table, 'warningUrgencyFlag', TRUE,
            12, 13, 9, 10,
            1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersDivert2Tab {

        # Parameters Divert2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # 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($table, '<b>Background colour parameters (2/2)</b>',
            0, 13, 0, 1);

        $self->addLabel($table, '<u>All other channels</u>',
            1, 3, 1, 2);
        $self->addComboBox($table, 'otherAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 5, 1, 2);
        $self->addLabel($table, 'Interval (seconds)',
            5, 7, 1, 2);
        $self->addEntryWithIcon($table, 'otherAlertInterval', 'int', 1, undef,
            7, 9, 1, 2,
            8, 8);              # Max width
        $self->addLabel($table, 'Sound',
            9, 11, 1, 2);
        $self->addComboBox($table, 'otherAlertSound', \@comboList2, '',
            FALSE,              # 'undef' value allowed
            11, 13, 1, 2);

        $self->addLabel($table, 'Character limit',
            1, 3, 2, 3);
        $self->addEntryWithIcon($table, 'otherCharLimit', 'int', 0, undef,
            3, 5, 2, 3,
            8, 8);              # Max width
        $self->addLabel($table, 'Show room number',
            5, 8, 2, 3);
        $self->addCheckButton($table, 'otherRoomFlag', TRUE,
            8, 9, 2, 3,
            1, 0.5);
        $self->addLabel($table, 'Show urgency hint',
            9, 12, 2, 3);
        $self->addCheckButton($table, 'otherUrgencyFlag', TRUE,
            12, 13, 2, 3,
            1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersInventory1Tab {

        # Parameters Inventory1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Object sensitivity
        $self->addLabel($table, '<b>Object sensitivity</b>',
            0, 12, 0, 1);

        $self->addLabel(
            $table, 'How closely model objects much match, when compared (0-100)',
            1, 9, 1, 2);
        $self->addEntryWithIcon($table, 'sensitivity', 'int', 0, 100,
            9, 12, 1, 2);

        $self->addLabel(
            $table, '<i>0 - any objects match, 100 - only identical objects match,</i>',
            2, 12, 2, 3);
        $self->addLabel(
            $table, '<i>70 - objects must be fairly similar to match</i>',
            2, 12, 3, 4);
        $self->addLabel(
            $table, '<i>90 - objects must be very similar to match</i>',
            2, 12, 4, 5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersLocator1Tab {

        # Parameters Locator1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Task window
        $self->addLabel($table, '<b>Task window</b>',
            0, 12, 0, 1);

        $self->addLabel(
            $table, 'Description max characters',
            1, 9, 1, 2);
        $self->addEntryWithIcon($table, 'winDescripLimit', 'int', 0, undef,
            9, 12, 1, 2,
            8, 8);

        $self->addLabel(
            $table, '<i>(leave empty to display the whole description)</i>',
            9, 12, 2, 3);
        $self->addLabel($table, 'Show multiple corpses on single line',
            1, 11, 2, 3);
        $self->addCheckButton($table, 'combineCorpseFlag', TRUE,
            11, 12, 3, 4);
        $self->addLabel($table, 'Show multiple body parts on single line',
            1, 11, 4, 5);
        $self->addCheckButton($table, 'combineBodyPartFlag', TRUE,
            11, 12, 4, 5);
        $self->addLabel($table, 'When listing objects, show nouns first',
            1, 11, 5, 6);
        $self->addCheckButton($table, 'showParsedFlag', TRUE,
            11, 12, 5, 6);

        $self->addLabel($table, '<b>Manual resets</b>',
            0, 12, 6, 7);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'do_nothing\'', 'autoLookMode',
            'do_nothing',   # IV set to this value when toggled
            TRUE,           # Sensitive widget
            1, 3, 7, 8);
        $self->addLabel(
            $table,
            'Look for room statements only in lines received after the manual reset',
            3, 12, 7, 8);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'search_back\'', 'autoLookMode', 'search_back', TRUE,
            1, 3, 8, 9);
        $self->addLabel(
            $table,
            'After a manual reset, look for most recently-received room statement',
            3, 12, 8, 9);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'send_look\'', 'autoLookMode', 'send_look', TRUE,
            1, 3, 9, 10);
        $self->addLabel(
            $table,
            'After a manual reset, send a \'look\' command and use the response',
            3, 12, 9, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersStatus1Tab {

        # Parameters Status1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # (Import a list of standard colour tags, putting bold colours first)
        @comboList = ($axmud::CLIENT->constColourTagList, $axmud::CLIENT->constBoldColourTagList);

        # Background colour parameters
        $self->addLabel($table, '<b>Background colour parameters</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Allow background colour changes',
            1, 5, 1, 2);
        $self->addCheckButton($table, 'allowColourFlag', TRUE,
            5, 6, 1, 2);
        $self->addLabel($table, 'Character alive',
            1, 3, 2, 3);
        $self->addComboBox($table, 'aliveColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 2, 3);
        $self->addLabel($table, 'Alive, health below 50%',
            1, 3, 3, 4);
        $self->addComboBox($table, 'alive50Colour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 3, 4);
        $self->addLabel($table, 'Alive, health below 30%',
            1, 3, 4, 5);
        $self->addComboBox($table, 'alive30Colour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 4, 5);
        $self->addLabel($table, 'Alive, health below 10%',
            1, 3, 5, 6);
        $self->addComboBox($table, 'alive10Colour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 5, 6);

        # (right column)
        $self->addLabel($table, 'Character asleep',
            7, 9, 2, 3);
        $self->addComboBox($table, 'asleepColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 2, 3);
        $self->addLabel($table, 'Character passed out',
            7, 9, 3, 4);
        $self->addComboBox($table, 'passedOutColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 3, 4);
        $self->addLabel($table, 'Character dead',
            7, 9, 4, 5);
        $self->addComboBox($table, 'deadColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            9, 12, 4, 5);

        # Data display
        $self->addLabel($table, '<b>Data display</b>',
            0, 12, 6, 7);
        $self->addLabel($table, 'Show information in \'main\' window gauges',
            1, 5, 7, 8);
        $self->addCheckButton($table, 'gaugeFlag', TRUE,
            5, 6, 7, 8);
        $self->addLabel($table, 'Also show explicit gauge labels',
            2, 5, 8, 9);
        $self->addCheckButton($table, 'gaugeValueFlag', TRUE,
            5, 6, 8, 9);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersStatus2Tab {

        # Parameters Status2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->parametersStatus2Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Status task commands
        $self->addLabel($table, '<b>Commify numbers</b>',
            0, 12, 0, 1);

        my ($group, $radioButton) = $self->addRadioButton(
            $table, undef, '\'none\'', 'commifyMode',
            'none',     # IV set to this value when toggled
            TRUE,       # Sensitive widget
            1, 3, 1, 2);
        $self->addLabel($table, 'Don\'t add commas to numbers (1000000)',
            3, 12, 1, 2);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'use_comma\'', 'commifyMode', 'use_comma', TRUE,
            1, 3, 2, 3);
        $self->addLabel($table, 'Add commas to numbers (1,000,000)',
            3, 12, 2, 3);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'use_europe\'', 'commifyMode', 'use_europe', TRUE,
            1, 3, 3, 4);
        $self->addLabel($table, 'Add European-style full stops to numbers (1.000.000)',
            3, 12, 3, 4);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'use_brit\'', 'commifyMode', 'use_brit', TRUE,
            1, 3, 4, 5);
        $self->addLabel($table, 'Add British-style spaces to numbers (1 000 000)',
            3, 12, 4, 5);

        ($group, $radioButton) = $self->addRadioButton(
            $table, $group, '\'use_underline\'', 'commifyMode', 'use_underline', TRUE,
            1, 3, 5, 6);
        $self->addLabel($table, 'Add underlines to numbers (1_000_000)',
            3, 12, 5, 6);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersStatus3Tab {

        # Parameters Status3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->parametersStatus3Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Status task commands
        $self->addLabel($table, '<b>Status task commands</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 230);

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

        # Add entries/comboboxes for adding new commands
        $self->addLabel($table, 'Command',
            1, 2, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            2, 8, 8, 9);

        $self->addLabel($table, 'Interval',
            8, 9, 8, 9);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            9, 12, 8, 9, 4, 4);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub parametersWatch1Tab {

        # Parameters Watch1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # (Import a list of standard colour tags, putting bold colours first)
        @comboList = ($axmud::CLIENT->constBoldColourTagList, $axmud::CLIENT->constColourTagList);

        # Background colour parameters
        $self->addLabel($table, '<b>Background colour parameters</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Default background colour',
            1, 3, 2, 3);
        $self->addComboBox($table, 'defaultColour', \@comboList, '',
            FALSE,              # 'undef' value allowed
            3, 6, 2, 3);
        $self->addLabel($table, '<i>(Leave empty to use the normal background colour)</i>',
            6, 12, 2, 3);

        $self->addLabel($table, 'Channels task alert colour',
            1, 3, 3, 4);
        $self->addComboBox($table, 'channelsAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 3, 4);
        $self->addLabel($table, 'Interval (seconds)',
            7, 9, 3, 4);
        $self->addEntryWithIcon($table, 'channelsAlertInterval', 'int', 1, undef,
            9, 12, 3, 4);

        $self->addLabel($table, 'Chat task alert colour',
            1, 3, 4, 5);
        $self->addComboBox($table, 'chatAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 4, 5);
        $self->addLabel($table, 'Interval (seconds)',
            7, 9, 4, 5);
        $self->addEntryWithIcon($table, 'chatAlertInterval', 'int', 1, undef,
            9, 12, 4, 5);

        $self->addLabel($table, 'Divert task alert colour',
            1, 3, 5, 6);
        $self->addComboBox($table, 'divertAlertColour', \@comboList, '',
            TRUE,               # No 'undef' value used
            3, 6, 5, 6);
        $self->addLabel($table, 'Interval (seconds)',
            7, 9, 5, 6);
        $self->addEntryWithIcon($table, 'divertAlertInterval', 'int', 1, undef,
            9, 12, 5, 6);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Actions', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Status task actions
        $self->addLabel($table, '<b>' . $self->editObj->prettyName . ' task actions</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>This task has no actions</i>',
            1, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsChannels1Tab {

        # Actions Channels1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Channels task actions
        $self->addLabel($table, '<b>Channels task actions</b>',
            0, 12, 0, 1);

        my $button = $self->addButton($table, 'Empy window', '', undef,
            1, 5, 1, 2,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptychannelswindow', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Empty the task window',
            6, 12, 1, 2);

        my $button2 = $self->addButton($table, '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($table, 'Edit channel patterns in the current world profile',
            6, 12, 2, 3);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsCompass1Tab {

        # Actions Compass1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Compass task actions
        $self->addLabel($table, '<b>Compass task actions</b>',
            0, 12, 0, 1);

        $self->addLabel($table, '<i>Current Compass task</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($table, 'Enable task', '', undef,
            2, 6, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('compass on', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Sends world commands when each keypad key is pressed',
            7, 12, 2, 3);

        my $button2 = $self->addButton($table, 'Disable task', '', undef,
            2, 6, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('compass off', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Keypad keys behave normally',
            7, 12, 3, 4);

        $self->addLabel($table, '<i>Current and/or initial Compasss task</i>',
            1, 12, 4, 5);

        my $button3 = $self->addButton($table, 'Enable task(s)', '', undef,
            2, 6, 5, 6,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('permcompass on', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Sends world commands when each keypad key is pressed',
            7, 12, 5, 6);

        my $button4 = $self->addButton($table, 'Disable task(s)', '', undef,
            2, 6, 6, 7,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('permcompass off', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Keypad keys behave normally',
            7, 12, 6, 7);

        $self->addLabel($table, '<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($table, 'Keypad key',
            2, 4, 8, 9);
        my $combo = $self->addComboBox($table, undef, \@comboList, 'Select key:',
            TRUE,               # No 'undef' value used
            4, 6, 8, 9);
        $self->addLabel($table, 'World command',
            7, 8, 8, 9);
        my $entry = $self->addEntry($table, undef, TRUE,
            8, 12, 8, 9);
        my $button5 = $self->addButton($table, '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($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsDivert1Tab {

        # Actions Divert1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Divert task actions
        $self->addLabel($table, '<b>Divert task actions</b>',
            0, 12, 0, 1);

        my $button = $self->addButton($table, 'Empy window', '', undef,
            1, 5, 1, 2,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptydivertwindow', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Empty the task window',
            6, 12, 1, 2);

        my $button2 = $self->addButton($table, '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($table, 'Edit channel patterns in the current world profile',
            6, 12, 2, 3);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsInventory1Tab {

        # Actions Inventory1 tab (also used with Condition task)
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Inventory/Condition task actions
        $self->addLabel($table, '<b>Inventory/Condition task actions</b>',
            0, 12, 0, 1);

        $self->addLabel($table, '<i>Active mode</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($table, 'Activate tasks', '', undef,
            2, 4, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('activateinventory', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Put the Inventory and/or Condition tasks in \'active\' mode',
            4, 12, 2, 3);

        my $button2 = $self->addButton($table, 'Disactivate tasks', '', undef,
            2, 4, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('disactivateinventory', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Put the Inventory and/or Condition tasks in \'disactivated\' mode',
            4, 12, 3, 4);

        $self->addLabel($table, '<i>Unprotected objects</i>',
            1, 12, 4, 5);

        my $button3 = $self->addButton($table, 'Sell all', '', undef,
            2, 4, 5, 6,
            TRUE,           # Irreversible
        );
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('sellall', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Sell all unprotected objects in inventory',
            4, 12, 5, 6);

        my $button4 = $self->addButton($table, 'Drop all', '', undef,
            2, 4, 6, 7,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('dropall', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Drop all unprotected objects in inventory',
            4, 12, 6, 7);

        my $button5 = $self->addButton($table, 'Use all', '', undef,
            2, 4, 7, 8,
            TRUE,           # Irreversible
        );
        $self->addLabel($table, 'Apply the command below to all unprotected objects in inventory',
            4, 12, 7, 8);

        my $entry = $self->addEntryWithIcon($table, 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(
            $table,
            '<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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsInventory2Tab {

        # Actions Inventory2 tab (also used with Condition task)
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Current inventory
        $self->addLabel($table, '<b>Current inventory</b>',
            0, 12, 0, 1);

        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 9,
            -1, 200);       # Fixed height

        # 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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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($table,
            'Protect objects matching:', 'Protect objects matching this list of words', undef,
            1, 4, 10, 11,
            TRUE,           # Irreversible
        );
        my $entry = $self->addEntryWithIcon($table, 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($table,
            'Monitor objects matching:', 'Monitor objects matching this list of words', undef,
            1, 4, 11, 12,
            TRUE,           # Irreversible
        );
        my $entry2 = $self->addEntryWithIcon($table, 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
        $vBox->pack_start($table, 0, 0, 0);

        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::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Locator task actions
        $self->addLabel($table, '<b>Locator task actions</b>',
            0, 12, 0, 1);

        $self->addLabel($table, '<i>Resets</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($table, 'Reset Locator task', '', undef,
            2, 5, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('resetlocatortask', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Reset the locator when it gets confused (or lost)',
            6, 12, 2, 3);

        $self->addLabel($table, '<i>Co-ordinated moves</i>',
            1, 12, 3, 4);

        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, 32,
            2, 4, 4, 5);
        my $button2 = $self->addButton($table, '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($table, 'Send a movement command to the world',
            6, 12, 4, 5);

        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, 32,
            2, 4, 5, 6);
        my $button3 = $self->addButton($table, '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($table, 'Send a non-movement command to the world',
            6, 12, 5, 6);

        $self->addLabel($table, '<i>Locator wizard</i>',
            1, 12, 6, 7);

        my $button7 = $self->addButton($table, 'Run Locator wizard...', '', undef,
            2, 5, 7, 8);
        $button7->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('locatorwizard', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Run the Locator task wizard for the current world',
            6, 12, 7, 8);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsLocator2Tab {

        # Actions Locator2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Locator task actions
        $self->addLabel($table, '<b>Locator task actions (2)</b>',
            0, 12, 0, 1);

        $self->addLabel($table, '<i>Unknown words</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($table, 'Toggle word collection', '', undef,
            2, 5, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('collectunknownwords', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Turn collection of unrecognised words on/off',
            6, 12, 2, 3);

        my $button2 = $self->addButton($table, 'Empty word list', '', undef,
            2, 5, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptyunknownwords', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Empty the list of unrecognised words',
            6, 12, 3, 4);

        my $button3 = $self->addButton($table, 'Display word list', '', undef,
            2, 5, 4, 5);
        $button3->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listunknownwords', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Display unrecognised words in the \'main\' window',
            6, 12, 4, 5);

        $self->addLabel($table, '<i>Contents lines</i>',
            1, 12, 5, 6);

        my $button4 = $self->addButton($table, 'Toggle contents collection', '', undef,
            2, 5, 6, 7,
            TRUE,           # Irreversible
        );
        $button4->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('collectcontentslines', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Turn collection of contents lines on/off',
            6, 12, 6, 7);

        my $button5 = $self->addButton($table, 'Empty contents list', '', undef,
            2, 5, 7, 8,
            TRUE,           # Irreversible
        );
        $button5->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptycontentslines', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Empty the list of contents lines',
            6, 12, 7, 8);

        my $button6 = $self->addButton($table, 'Display contents list', '', undef,
            2, 5, 8, 9);
        $button6->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('listcontentslines', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Display contents lines in the \'main\' window',
            6, 12, 8, 9);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsStatus1Tab {

        # Actions Status1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Status task actions
        $self->addLabel($table, '<b>Status task actions</b>',
            0, 12, 0, 1);

        $self->addLabel($table, '<i>Active mode</i>',
            1, 12, 1, 2);

        my $button = $self->addButton($table, 'Activate Status task', '', undef,
            2, 5, 2, 3,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('activatestatustask', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Put the Status task in \'active\' mode',
            6, 12, 2, 3);

        my $button2 = $self->addButton($table, 'Disactivate Status task', '', undef,
            2, 5, 3, 4,
            TRUE,           # Irreversible
        );
        $button2->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('disactivatestatustask', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Put the Status task in \'disactivated\' mode',
            6, 12, 3, 4);

        $self->addLabel($table, '<i>Counters</i>',
            1, 12, 4, 5);

        my $button3 = $self->addButton($table, '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($table, undef, \@comboList, 'Select counter:',
            TRUE,               # No 'undef' value used
            2, 4, 6, 7);
        my $button4 = $self->addButton($table, '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($table, 'Reset selected counters',
            6, 12, 6, 7);

        $self->addLabel($table, '<i>Local/remote wimpy level</i>',
            1, 12, 7, 8);

        my $entry = $self->addEntryWithIcon(
            $table, undef, 'int', 0, $self->session->currentWorld->constLocalWimpyMax,
            2, 4, 8, 9);
        my $button5 = $self->addButton($table, '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($table, 'Set local wimpy level (0..100)',
            6, 12, 8, 9);

        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 0, undef,
            2, 4, 9, 10);
        my $button6 = $self->addButton($table, '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($table, 'Set remote wimpy level (0..max)',
            6, 12, 9, 10);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub actionsWatch1Tab {

        # Actions Watch1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Watch task actions
        $self->addLabel($table, '<b>Watch task actions</b>',
            0, 12, 0, 1);

        my $button = $self->addButton($table, 'Empy window', '', undef,
            1, 5, 1, 2,
            TRUE,           # Irreversible
        );
        $button->signal_connect('clicked' => sub {

            $self->session->pseudoCmd('emptywatchwindow', $self->pseudoCmdMode);
        });
        $self->addLabel($table, 'Empty the task window',
            6, 12, 1, 2);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 {}        # Inherited from GA::Generic::ConfigWin

#   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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my @comboList;

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Toolbar button properties</b>',
            0, 12, 0, 1);

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

        $self->addLabel($table, 'Description (max 32 chars)',
            1, 3, 2, 3);
        if (! $self->editObj->customFlag) {

            my $entry = $self->addEntry($table, 'descrip', FALSE,
                3, 12, 2, 3);

        } else {

            $self->addEntryWithIcon($table, 'descrip', 'string', 1, 32,
                3, 12, 2, 3);
        }

        $self->addLabel($table, 'Instruction',
            1, 3, 3, 4);
        if (! $self->editObj->customFlag) {

            my $entry = $self->addEntry($table, 'descrip', FALSE,
                3, 12, 3, 4);

        } else {

            $self->addEntryWithIcon($table, 'instruct', 'string', 1, 32,
                3, 12, 3, 4);
        }

        $self->addLabel($table, 'Icon file (24x24 pixels)',
            1, 3, 4, 5);
        my $entry = $self->addEntry($table, 'iconPath', FALSE,
            3, 10, 4, 5);
        $entry->set_text($self->editObj->iconPath);
        my $button = $self->addButton(
            $table,
            '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->addLabel($table, 'Require a current session',
            1, 5, 5, 6);
        $self->addCheckButton($table, 'requireSessionFlag', FALSE,
            5, 6, 5, 6, 1, 0.5);

        # Right column
        $self->addLabel($table, 'Custom toolbar button',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'customFlag', FALSE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Require connection to a world',
            7, 11, 5, 6);
        my $checkButton = $self->addCheckButton($table, 'requireConnectFlag', TRUE,
            11, 12, 5, 6, 1, 0.5);
        # (Checkbutton desensitised if this isn't a custom toolbar button)
        if (! $self->editObj->customFlag) {

            $checkButton->set_sensitive(FALSE);
        }

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 {}        # 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->patternsTab();

        return 1;
    }

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

    # Notebook tabs

    sub nameTab {

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

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

        # Local variables
        my @comboList;

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Text-to-speech configuration
        $self->addLabel($table, '<b>Text-to-speech configuration</b>',
            0, 12, 0, 1);

        $self->addLabel($table, 'Name',
            1, 6, 1, 2);
        my $entry = $self->addEntry($table, 'name', FALSE,
            6, 12, 1, 2);
        $self->addLabel($table, 'Modifiable',
            6, 8, 2, 3);
        my $checkButton = $self->addCheckButton($table, undef, FALSE,
            8, 9, 2, 3);
        $self->addLabel($table, 'Deletable',
            9, 11, 2, 3);
        my $checkButton2 = $self->addCheckButton($table, undef, FALSE,
            11, 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(
            $table,
            'Engine name (must be installed on your system)',
            1, 6, 3, 4);

        @comboList = $axmud::CLIENT->constTTSList;
        my $comboBox = $self->addComboBox($table, '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($table, 'Voice',
            1, 6, 4, 5);
        my $entry2 = $self->addEntry($table, 'voice', TRUE,
            6, 10, 4, 5);
        my $button2 = $self->addButton(
            $table, '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($table, 'Speed',
            1, 6, 5, 6);
        my $entry3 = $self->addEntry($table, 'speed', TRUE,
            6, 10, 5, 6);
        my $button3 = $self->addButton(
            $table, '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($table, 'Rate',
            1, 6, 6, 7);
        my $entry4 = $self->addEntry($table, 'rate', TRUE,
            6, 10, 6, 7);
        my $button4 = $self->addButton(
            $table, '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($table, 'Pitch',
            1, 6, 7, 8);
        my $entry5 = $self->addEntry($table, 'pitch', TRUE,
            6, 10, 7, 8);
        my $button5 = $self->addButton(
            $table, '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($table, 'Volume',
            1, 6, 8, 9);
        my $entry6 = $self->addEntry($table, 'volume', TRUE,
            6, 10, 8, 9);
        my $button6 = $self->addButton(
            $table, '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($table, 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(
            $table, '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 (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 Gtk2::Labels to set/reset
        #   $entry2, $entry3, $entry4, $entry5, $entry6
        #                               - Five Gtk2::Entry boxes to set/reset
        #   $button2, $button3, $button4, $button5, $button6
        #                               - Five Gtk2::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 for default speed)');
            $label4->set_markup('Word rate (not available with eSpeak)');
            $label5->set_markup('Word pitch (value in the range 0 - 99)');
            $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 10-200)');
            $label4->set_markup('Word rate (not available with espeak-ng)');
            $label5->set_markup('Word pitch (value in the range 0 - 99)');
            $label6->set_markup('Volume (value in the range 0-200)');

        } 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.5 - 2)');
            $label5->set_markup('Word pitch (not available with Festival)');
            $label6->set_markup('Volume (value in the range 0.33 - 6)');

        } 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 100-400)');
                $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.5 - 2)');
                $label5->set_markup('Word pitch (value in the range 0.1 - 5)');
                $label6->set_markup('Volume (value in the range 0.33 - 6)');
            }

        } 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 ($vBox, $table) = $self->addTab('_Patterns', $self->notebook);

        # Text-to-speech (TTS) (cont.)
        $self->addLabel($table, '<b>Text-to-speech (TTS) (cont.)</b>',
            0, 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(
            $table,
            '<i>Exclusive patterns: only convert lines which match these patterns (if not'
            . ' set, all lines converted)</i>',
            1, 12, 1, 2);
        $self->addTextView($table, 'exclusiveList', $flag,
            1, 12, 2, 6,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 160);                  # Fixed height

        $self->addLabel(
            $table,
            '<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($table, 'excludedList', $flag,
            1, 12, 7, 12,
            TRUE, TRUE, FALSE, FALSE,  # Treat as list, remove empty lines, don't remove whitespace
            -1, 160);                  # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Standard window object settings</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Window number',
            1, 3, 1, 2);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 1, 2);
        $self->addLabel($table, 'Window category',
            1, 3, 2, 3);
        $self->addEntry($table, 'winCategory', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'Window type',
            1, 3, 3, 4);
        $self->addEntry($table, 'winType', FALSE,
            3, 6, 3, 4);
        $self->addLabel($table, 'Internal window name',
            1, 3, 4, 5);
        $self->addEntry($table, 'winName', FALSE,
            3, 6, 4, 5);

        $ownerObj = $self->editObj->owner;
        $self->addLabel($table, 'Owner',
            1, 3, 5, 6);
        my $entry = $self->addEntry($table, 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($table, 'Session',
            1, 3, 6, 7);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            3, 6, 6, 7);
        $entry2->set_text($self->editObj->session->number);

        $self->addLabel($table, 'Workspace',
            1, 3, 7, 8);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            3, 6, 7, 8);
        $entry3->set_text($self->editObj->workspaceObj->number);

        $self->addLabel($table, 'Child \'free\' windows',
            1, 3, 8, 9);
        my $entry4 = $self->addEntry($table, undef, FALSE,
            3, 6, 8, 9);
        $entry4->set_text($self->editObj->ivPairs('childFreeWinHash'));

        # Right column
        $self->addLabel($table, 'Window enabled (actually created)',
            7, 11, 1, 2);
        $self->addCheckButton($table, 'enabledFlag', FALSE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Window visible on workspace',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'visibleFlag', FALSE,
            11, 12, 2, 3, 1, 0.5);

        $self->addLabel($table, 'Gtk2 window set',
            7, 11, 3, 4);
        my $checkButton = $self->addCheckButton($table, undef, FALSE,
            11, 12, 3, 4, 1, 0.5);
        if ($self->editObj->winWidget) {

            $checkButton->set_active(TRUE);
        }

        $self->addLabel($table, 'Wnck window set',
            7, 11, 4, 5);
        my $checkButton2 = $self->addCheckButton($table, undef, FALSE,
            11, 12, 4, 5, 1, 0.5);
        if ($self->editObj->wnckWin) {

            $checkButton2->set_active(TRUE);
        }

        $self->addLabel($table, 'Packing box set',
            7, 11, 5, 6);
        my $checkButton3 = $self->addCheckButton($table, undef, FALSE,
            11, 12, 5, 6, 1, 0.5);
        if ($self->editObj->packingBox) {

            $checkButton3->set_active(TRUE);
        }

       # Tab complete
       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Grid', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>\'Grid\' window settings</b>',
            1, 6, 0, 1);
        $self->addLabel($table, 'Workspace grid',
            1, 3, 1, 2);
        my $entry = $self->addEntry($table, 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($table, 'Area object',
            1, 3, 2, 3);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            3, 6, 2, 3);
        if (! $areaObj) {

            $entry2->set_text('(not set)');

        } else {

            $entry2->set_text($areaObj->number);

            $self->addLabel($table, 'Zone',
                1, 3, 3, 4);
            my $entry3 = $self->addEntry($table, undef, FALSE,
                3, 6, 3, 4);
            if (! $areaObj->zoneObj) {
                $entry3->set_text('(not set)');
            } else {
                $entry3->set_text($areaObj->zoneObj->number);
            }

            $self->addLabel($table, '<i>Position in zone\'s internal grid</i>',
                1, 3, 4, 5);
            $self->addLabel($table, 'Layer',
                1, 3, 5, 6);
            my $entry4 = $self->addEntry($table, undef, FALSE,
                3, 6, 5, 6);
            $entry4->set_text($areaObj->layer);

            $self->addLabel($table, 'Top-left X (blocks)',
                1, 3, 6, 7);
            my $entry5 = $self->addEntry($table, undef, FALSE,
                3, 6, 6, 7);
            $entry5->set_text($areaObj->leftBlocks);

            $self->addLabel($table, 'Top-left Y (blocks)',
                1, 3, 7, 8);
            my $entry6 = $self->addEntry($table, undef, FALSE,
                3, 6, 7, 8);
            $entry6->set_text($areaObj->topBlocks);

            $self->addLabel($table, 'Bottom-right X (blocks)',
                1, 3, 8, 9);
            my $entry7 = $self->addEntry($table, undef, FALSE,
                3, 6, 8, 9);
            $entry7->set_text($areaObj->rightBlocks);

            $self->addLabel($table, 'Bottom-right Y (blocks)',
                1, 3, 9, 10);
            my $entry8 = $self->addEntry($table, undef, FALSE,
                3, 6, 9, 10);
            $entry8->set_text($areaObj->bottomBlocks);

            $self->addLabel($table, 'Width (blocks)',
                1, 3, 10, 11);
            my $entry9 = $self->addEntry($table, undef, FALSE,
                3, 6, 10, 11);
            $entry9->set_text($areaObj->widthBlocks);

            $self->addLabel($table, 'Height (blocks)',
                1, 3, 11, 12);
            my $entry10 = $self->addEntry($table, undef, FALSE,
                3, 6, 11, 12);
            $entry10->set_text($areaObj->heightBlocks);
        }

        # Right column
        $self->addLabel($table, 'Winmap',
            7, 9, 1, 2);
        my $entry10 = $self->addEntry($table, 'winmap', FALSE,
            9, 12, 1, 2);
        my $button = $self->addButton($table, '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($table, '<i>Position on workspace</i>',
                7, 9, 4, 5);
            $self->addLabel($table, 'Top-left X (pixels)',
                7, 9, 5, 6);
            my $entry11 = $self->addEntry($table, undef, FALSE,
                9, 12, 5, 6);
            $entry11->set_text($areaObj->xPosPixels);

            $self->addLabel($table, 'Top-left Y (pixels)',
                7, 9, 6, 7);
            my $entry12 = $self->addEntry($table, undef, FALSE,
                9, 12, 6, 7);
            $entry12->set_text($areaObj->yPosPixels);

            $self->addLabel($table, 'Width (pixels)',
                7, 9, 7, 8);
            my $entry13 = $self->addEntry($table, undef, FALSE,
                9, 12, 7, 8);
            $entry13->set_text($areaObj->widthPixels);

            $self->addLabel($table, 'Height (pixels)',
                7, 9, 8, 9);
            my $entry14 = $self->addEntry($table, undef, FALSE,
                9, 12, 8, 9);
            $entry14->set_text($areaObj->heightPixels);
        }

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Free', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>\'Free\' window settings</b>',
            1, 6, 0, 1);
        $self->addLabel($table, 'Default width (pixels)',
            1, 3, 1, 2);
        $self->addEntry($table, 'widthPixels', FALSE,
            3, 6, 1, 2, 8, 8);
        $self->addLabel($table, 'Default height (pixels)',
            1, 3, 2, 3);
        $self->addEntry($table, 'heightPixels', FALSE,
            3, 6, 2, 3, 8, 8);

        $self->addLabel($table, '<i>Window customisation settings</i>',
            1, 3, 3, 4);

        # Add a simple list
        @columnList = (
            'Key', 'text',
            'Value', 'text',
        );

        my $slWidget = $self->addSimpleList($table, 'configHash', \@columnList,
            1, 12, 4, 12,
            -1, 180);     # Fixed height

        # Right column
        $self->addLabel($table, 'Default border size (pixels)',
            7, 9, 1, 2);
        $self->addEntry($table, 'borderPixels', FALSE,
            9, 12, 1, 2, 8, 8);
        $self->addLabel($table, 'Default spacing size (pixels)',
            7, 9, 2, 3);
        $self->addEntry($table, 'spacingPixels', FALSE,
            9, 12, 2, 3, 8, 8);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Internal', $self->notebook);

        # Add tabs to the inner notebook
        $self->internal1Tab($innerNotebook);
        $self->internal2Tab($innerNotebook);

        return 1;
    }

    sub internal1Tab {

        # Internal1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # 'Internal' window settings
        $self->addLabel($table, '<b>\'Internal\' window settings</b>',
            1, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 4, 10,
            -1, 250);     # Fixed height

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

        # Add some buttons
        my $button = $self->addButton($table, '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($table, 'Update', 'Update the list', undef,
            10, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub internal1Tab_refreshList {

        # Resets the simple list displayed by $self->internal1Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # 'Internal' window settings
        $self->addLabel($table, '<b>\'Internal\' window settings (2)</b>',
            1, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 4, 10,
            -1, 250);     # Fixed height

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

        # Add some buttons
        my $button = $self->addButton($table, '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($table, 'Update', 'Update the list', undef,
            10, 12, 10, 11);
        $button2->signal_connect('clicked' => sub {

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub internal2Tab_refreshList {

        # Resets the simple list displayed by $self->internal2Tab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 table size
        my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # 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->{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->{drawingArea2} = undef;
        $self->{editButton} = undef;
        $self->{deleteButton} = undef;

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Winmap settings
        $self->addLabel($table, '<b>Winmap settings</b>',
            0, 6, 0, 1);
        $self->addLabel($table, 'Winmap name',
            1, 3, 1, 2);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 1, 2, 16, 16);

        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 3, 10,
            -1, 270);

        # 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($table, 'World',
            1, 2, 10, 11);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('worldProfHash'));
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,              # no 'undef' value allowed
            2, 4, 10, 11);

        my $button = $self->addButton($table, '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(
            $table,
            '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($table, '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($table, '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 (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub nameTab_refreshList {

        # Called by $self->nameTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 ($vBox, $table) = $self->addTab('_Strips', $self->notebook);

        # 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($table, '<b>Strip objects</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>List of strip objects for the window, arranged</i>',
            1, 6, 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($table, undef, \@comboList, '',
            TRUE,              # 'undef' value not allowed
            6, 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($table, undef, \@columnList,
            1, 12, 2, 4,
            -1, 160);

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

        # Add editing widgets
        @comboList2 = $self->stripsTab_getComboList();
        my $comboBox2 = $self->addComboBox($table, undef, \@comboList2, '',
            TRUE,              # no 'undef' value allowed
            1, 6, 6, 7);

        my $button = $self->addButton(
            $table,
            'Add to top',
            'Add strip object using specified initialisation settings',
            undef,
            6, 8, 6, 7);

        my $button2 = $self->addButton(
            $table,
            'Add to bottom',
            'Add strip object using specified initialisation settings',
            undef,
            8, 10, 6, 7);

        my $button3 = $self->addButton(
            $table,
            'Clear list',
            'Empty the list of strip objects',
            undef,
            10, 12, 6, 7);

        my $button4 = $self->addButton(
            $table,
            'Move up',
            'Move the selected strip object up one position in the list',
            undef,
            1, 3, 7, 8);

        my $button5 = $self->addButton(
            $table,
            'Move down',
            'Move the selected strip object down one position in the list',
            undef,
            3, 6, 7, 8);

        my $button6 = $self->addButton(
            $table,
            'Delete',
            'Delete the selected strip object',
            undef,
            6, 8, 7, 8);

        my $button7 = $self->addButton(
            $table,
            'Reverse',
            'Reverse the order of strip objects in the list',
            undef,
            8, 10, 7, 8);

        my $button8 = $self->addButton(
            $table,
            'Reset list',
            'Reset the list of strip objects',
            undef,
            10, 12, 7, 8);

        $self->addLabel(
            $table,
            '<i>Modify initialisation settings (overriding the default ones) for the selected strip'
            . ' object:</i>',
            1, 12, 8, 9);

        $self->addLabel($table, 'Initialisation key/value',
            1, 2, 9, 10);
        @comboList3 = ($noStripString);
        my $comboBox3 = $self->addComboBox($table, undef, \@comboList3, '',
            TRUE,              # no 'undef' value allowed
            2, 6, 9, 10);
        $comboBox3->set_sensitive(FALSE);

        my $entry = $self->addEntry($table, undef, TRUE,
            6, 10, 9, 10);
        $entry->set_sensitive(FALSE);

        my $button9 = $self->addButton(
            $table,
            'Use value',
            'Use the specified initialisation key with the value above',
            undef,
            10, 12, 9, 10);
        $button9->set_sensitive(FALSE);

        my $button10 = $self->addButton(
            $table,
            '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(
            $table,
            'Use \'undef\'',
            'Use the specified initialisation key with an \'undef\' value',
            undef,
            6, 8, 10, 11);
        $button11->set_sensitive(FALSE);

        my $button12 = $self->addButton(
            $table,
            'Use TRUE',
            'Use the specified initialisation key with a TRUE value',
            undef,
            8, 10, 10, 11);
        $button12->set_sensitive(FALSE);

        my $button13 = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub stripsTab_refreshList {

        # Called by $self->stripsTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget       - The GA::Gtk::Simple::List
        #   $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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Winzones', $self->notebook);

        # Add tabs to the inner notebook
        $self->winzones1Tab($innerNotebook);
        $self->winzones2Tab($innerNotebook);

        return 1;
    }

    sub winzones1Tab {

        # Winzones1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Import IVs (for convenience)
        %packageHash = reverse ($axmud::CLIENT->customTableHash);

        # We're going to draw a grid full of pretty colours, using a pixmap and a Gtk2::DrawingArea
        #   object

        ########
        # Part 1 - draw the 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 around 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 area, containing the main grid - a 60x60 grid containing zones
        #   $self->winzones1Tab_refreshGrid    - Draws the grid
        #   $self->winzones1Tab_getMouseClick  - Responds to mouse clicks on the area
        #   $self->winzones1Tab_getMouseMotion - Responds to mouse motion over the are
        $widthRequest = $self->borderX + ($self->gridSize * $self->cellWidth) + 30;
        $heightRequest = $self->borderY + ($self->gridSize * $self->cellHeight) + 50;

        my $drawingAreaObj = $self->addDrawingArea(
            $table,
            '',
            'winzones1Tab_refreshGrid',
            'winzones1Tab_getMouseClick',
            'winzones1Tab_getMouseMotion',
            FALSE, FALSE,                       # No scrolling
            0, 8, 0, 12,                        # Position in table
            $widthRequest, $heightRequest,      # Requested size of viewport
        );

        ########
        # Part 2 - widgets for adding new zones

        $self->addLabel($table, '<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($table,
            undef, 'Use 5-block clicks', undef, undef, TRUE,
            8, 12, 1, 2);

        ($radioGroup, $radioButton2) = $self->addRadioButton($table,
            $radioGroup, 'Use 1-block clicks', undef, undef, TRUE,
            8, 12, 2, 3);

        $self->addLabel($table, 'Top-left corner',
            8, 10, 3, 4);
        my $entry = $self->addEntry($table, undef, FALSE,
            10, 11, 3, 4, 3, 3);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            11, 12, 3, 4, 3, 3);

        $self->addLabel($table, 'Bottom-right corner',
            8, 10, 4, 5);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            10, 11, 4, 5, 3, 3);
        my $entry4 = $self->addEntry($table, undef, FALSE,
            11, 12, 4, 5, 3, 3);

        @comboList = (sort {lc($a) cmp lc($b)} (keys %packageHash));
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,              # no 'undef' value allowed
            8, 12, 5, 6);

        # Save these entry boxes as IVs, so they can be updated as the mouse moves over the drawing
        #   area
        $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($table,
            'Add winzone', 'Create a winzone using the selected area', undef,
            8, 10, 6, 7,
            TRUE,           # Irreversible
        );
        my $button2 = $self->addButton($table,
            'Cancel', 'Don\'t create a winzone using the selected area', undef,
            10, 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($table, '<b>Winzone actions</b>',
            8, 12, 7, 8);

        # When clicking on an existing zone, the winzone's number is displayed...
        $self->addLabel($table, 'Selected winzone #',
            8, 10, 8, 9);
        my $entry5 = $self->addEntry($table, undef, FALSE,
            10, 11, 8, 9, 3, 3);
        $self->ivPoke('entryCurrentZone', $entry5);

        # ...as is its colour
        my $drawingAreaObj2 = $self->addDrawingArea($table, '',
            undef, undef, undef,    # No function references
            FALSE, FALSE,           # No scrolling
            11, 12, 8, 9,           # Position in table
            15, 15,                 # Width/height request
        );

        $self->ivPoke('drawingArea2', $drawingAreaObj2);

        # Other widgets
        my $entry6 = $self->addEntry($table, undef, FALSE,
            8, 12, 9, 10);
        $self->ivPoke('entryCurrentTable', $entry6);

        my $button3 = $self->addButton($table, 'Edit...', 'Edit the selected winzone', undef,
            8, 10, 10, 11);
        my $button4 = $self->addButton($table, 'Delete', 'Delete the selected winzone', undef,
            10, 12, 10, 11,
            TRUE,           # Irreversible
        );
        my $button5 = $self->addButton($table,
            'Dump list', 'Display the list of winzones in the \'main\' window', undef,
            8, 10, 11, 12);
        my $button6 = $self->addButton($table, 'Refresh grid', 'Refresh the winmap grid', undef,
            10, 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,
            );

            # Re-draw the grid and all existing winzones (including the new one, if it was created),
            #   and return to mouse step 1
            $self->winzones1Tab_resetMouseStep($drawingAreaObj);
        });

        # Cancel button
        $button2->signal_connect('clicked' => sub {

            # Re-draw the grid and all existing winzones, and return to step 1
            $self->winzones1Tab_resetMouseStep($drawingAreaObj);
        });

        # 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_resetMouseStep($drawingAreaObj);
        });

        # 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_resetMouseStep($drawingAreaObj);
                }
            }
        });

        # 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($drawingAreaObj);
        });

        # 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($drawingAreaObj);

            if ($radioButton->get_active()) {
                $self->ivPoke('clickMode', 1);
            } else {
                $self->ivPoke('clickMode', 0);
            }
        });

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 area, or because they clicked the
        #   'cancel' button, or because they changed from 1-block clicks to 5-block clicks (or
        #   vice-versa)
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Re-draw the grid and all existing zones, removing the selected area
        $self->winzones1Tab_refreshGrid($drawingAreaObj);

        # 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('');

        $self->drawingArea2->drawRectangle(
            [0, 0, 50, 50],
            'white',
            TRUE,                   # Rectangle filled in
        );

        # Move back to the first step
        $self->ivPoke('mouseStep', 1);

        return 1;
    }

    sub winzones1Tab_refreshGrid {

        # Called by $self->addDrawingArea (inherited from GA::Generic::Win), following a call from
        #   $self->winzones1Tab
        # Draws (or re-draws) the grid, and fills in parts of the grid to show existing winzones
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $count,
            @zoneList, @sortedList, @colourList,
        );

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

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

        # Draw a white rectangle, filled in, to draw over anything that already exists
        $drawingAreaObj->drawRectangle(
            [
                $self->borderX,
                $self->borderY,
                # (+1 to remove the bottom and right black lines)
                ($self->gridSize * $self->cellWidth) + 1,
                ($self->gridSize * $self->cellHeight) + 1,
            ],
            'white',
            TRUE,           # Rectangle filled in
        );

        # Draw a 60x60 grey grid

        # Draw vertical lines
        for (
            my $x = $self->borderX;
            $x <= ($self->borderX + ($self->gridSize * $self->cellWidth));
            $x += $self->cellWidth
        ) {
            $drawingAreaObj->drawLine(
                [
                    $x,
                    $self->borderY,
                    $x,
                    ($self->borderY + ($self->gridSize * $self->cellHeight))
                ],
                'gray',
            );
        }

        # Draw horizontal lines
        for (
            my $y = $self->borderY;
            $y <= ($self->borderY + ($self->gridSize * $self->cellHeight));
            $y += $self->cellHeight
        ) {
            $drawingAreaObj->drawLine(
                [
                    $self->borderX,
                    $y,
                    ($self->borderX + ($self->gridSize * $self->cellWidth)),
                    $y
                ],
                'gray',
            );
        }

        # Draw a black grid, overlaid on the grey grid, on every 5th line

        # Draw vertical lines
        for (
            my $x = $self->borderX;
            $x <= ($self->borderX + ($self->gridSize * $self->cellWidth));
            $x += ($self->cellWidth * 5)
        ) {
            $drawingAreaObj->drawLine(
                [
                    $x,
                    $self->borderY,
                    $x,
                    ($self->borderY + ($self->gridSize * $self->cellHeight))
                ],
                'black',
            );
        }

        # Draw horizontal lines
        for (
            my $y = $self->borderY;
            $y <= ($self->borderY + ($self->gridSize * $self->cellHeight));
            $y += ($self->cellHeight * 5)
        ) {
            $drawingAreaObj->drawLine(
                [
                    $self->borderX,
                    $y,
                    ($self->borderX + ($self->gridSize * $self->cellWidth)),
                    $y
                ],
                'black',
            );
        }

        # Draw each existing winzone in turn
        @zoneList = $self->editObj->ivKeys('zoneHash');
        # Sort the list numerically
        @sortedList = sort {$a <=> $b} (@zoneList);
        # 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
        foreach my $winzoneNum (@sortedList) {

            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(
                $drawingAreaObj,
                $winzoneObj->left,
                $winzoneObj->top,
                $winzoneObj->right,
                $winzoneObj->bottom,
                $colour,
            );
        }

        # Operation complete
        return 1;
    }

    sub winzones1Tab_getMouseClick {

        # Called by $self->addDrawingArea (inherited from GA::Generic::Win), whenever the user
        #   clicks on the drawing area
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $xPos, $yPos    - The coordinates on the drawing area at which the user clicked
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $drawingAreaObj, $xPos, $yPos, $check) = @_;

        # Local variables
        my (
            $xGrid, $yGrid, $winzoneObj, $number, $posn,
            @colourList, @sortedList,
        );

        # Check for improper arguments
        if (! defined $drawingAreaObj || ! defined $xPos || ! defined $yPos || defined $check) {

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

        # Convert $xPos and $yPos - coordinates in the drawing area - 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($drawingAreaObj);

                # 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);
                }

                $self->drawingArea2->drawRectangle(
                    [0, 0, 50, 50],
                    $colourList[$posn],
                    TRUE,           # Rectangle filled in
                );

                # 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
                    $self->winzones1Tab_fillBlock($drawingAreaObj, $xGrid, $yGrid, 'pink');

                } else {

                    # Mark a 5x5 group of gridblocks pink
                    $self->winzones1Tab_fillZone(
                        $drawingAreaObj,
                        $self->startX,
                        $self->startY,
                        $self->startX + 4,
                        $self->startY + 4,
                        'pink',
                    );
                }

                # If the number of a zone that's been clicked on is displayed, remove it
                $self->entryCurrentZone->set_text('');
                $self->entryCurrentTable->set_text('');

                $self->drawingArea2->drawRectangle(
                    [0, 0, 50, 50],
                    'white',
                    TRUE,       # Rectangle filled in
                );

                # 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->winzones1Tab_fillZone(
                    $drawingAreaObj,
                    $self->startX,
                    $self->startY,
                    $self->stopX,
                    $self->stopY,
                    'pink',
                );

                # 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($drawingAreaObj);
            }
        }

        return 1;
    }

    sub winzones1Tab_getMouseMotion {

        # Called by $self->addDrawingArea (inherited from GA::Generic::Win), whenever the user moves
        #   the mouse pointer over the drawing area
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $xPos, $yPos    - The current coordiantes of the mouse over the drawing area
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $drawingAreaObj, $xPos, $yPos, $check) = @_;

        # Local variables
        my ($xGrid, $yGrid);

        # Check for improper arguments
        if (! defined $drawingAreaObj || ! defined $xPos || ! defined $yPos || defined $check) {

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

        # Convert $xPos and $yPos - coordinates in the drawing area - 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_fillBlock {

        # Called by various functions of this 'edit' window object
        # Fills in a single block of the grid in a single colour
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $xPos, $yPos    - The coordinates of the gridblock to be filled in
        #   $colour         - The colour, e.g. 'red'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $drawingAreaObj, $xPos, $yPos, $colour, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $drawingAreaObj || ! defined $xPos || ! defined $yPos || ! defined $colour
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->winzones1Tab_fillBlock', @_);
        }

        # Fill the square at grid coordinates $xPos, $yPos
        $drawingAreaObj->drawRectangle(
            [
                $self->borderX + ($xPos * $self->cellWidth) + 1,
                $self->borderY + ($yPos * $self->cellHeight) + 1,
                $self->cellWidth - 1,
                $self->cellHeight - 1,
            ],
            $colour,
            TRUE,           # Rectangle filled in
        );

        return 1
    }

    sub winzones1Tab_fillZone {

        # Called by various functions of this 'edit' window object
        # Fills in an area of the grid in a single colour, representing a particular winzone
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $startX, $startY, $stopX, $stopY
        #                   - The area of the grid to fill in
        #   $colour         - The colour, e.g. 'red'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my (
            $self, $drawingAreaObj, $startX, $startY, $stopX, $stopY, $colour,
            $check
        ) = @_;

        # Check for improper arguments
        if (
            ! defined $drawingAreaObj || ! defined $startX || ! defined $startY || ! defined $stopX
            || ! defined $stopY || ! defined $colour || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->winzones1Tab_fillZone', @_);
        }

        # Fill the area bounded by $startX, $startY, $stopX, $stopY
        for (my $xPos = $startX; $xPos <= $stopX; $xPos++) {

            for (my $yPos = $startY; $yPos <= $stopY; $yPos++) {

                # Fill the square at grid coordinates $xPos, $yPos
                $drawingAreaObj->drawRectangle(
                    [
                        $self->borderX + ($xPos * $self->cellWidth) + 1,
                        $self->borderY + ($yPos * $self->cellHeight) + 1,
                        $self->cellWidth - 1,
                        $self->cellHeight - 1,
                    ],
                    $colour,
                    TRUE,       # Rectangle filled in
                );
            }
        }

        return 1
    }

    sub winzones2Tab {

        # Winzones2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Resize
        $self->addLabel($table, '<b>Zone size settings</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Standard size for adding new table objects to an existing window, if a default size'
            . ' is required. Use a size</i>',
            1, 12, 1, 2);
        $self->addLabel(
            $table,
            '<i>which will fill up a 60x60 grid without leaving gaps, e.g. 10x10, 15x15, 30x15,'
            . ' 20x20, 12x60, 60x5 etc)</i>',
            2, 12, 2, 3);

        $self->addLabel($table, 'Zone width (1-60)',
            2, 4, 3, 4);
        $self->addEntryWithIcon($table, 'zoneWidth', 'int', 1, 60,
            4, 6, 3, 4);
        $self->addLabel($table, 'Zone height (1-60)',
            7, 9, 3, 4);
        $self->addEntryWithIcon($table, 'zoneHeight', 'int', 1, 60,
            9, 12, 3, 4);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 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 drawingArea2
        { $_[0]->{drawingArea2} }
    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
#       my ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Winzone settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table, 'Winzone number',
            1, 3, 1, 2);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 1, 2);

        $self->addLabel($table, 'Contains table object',
            1, 3, 2, 3);
        my $entry = $self->addEntry($table, undef, FALSE,
            3, 6, 2, 3);
        $entry->set_text($axmud::CLIENT->ivShow('customTableHash', $self->editObj->packageName));

        $self->addLabel($table, 'Package name',
            1, 3, 3, 4);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            3, 6, 3, 4);
        $entry2->set_text($self->editObj->packageName);

        $self->addLabel($table, 'Optional winzone name',
            1, 3, 4, 5);
        $self->addEntryWithButton($table, 'objName', TRUE,
            3, 6, 4, 5);

        # Right column
        $self->addLabel($table, 'Parent winmap',
            7, 9, 1, 2);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            9, 12, 1, 2);
        $entry3->set_text($self->editObj->winmapObj->name);

        $self->addLabel($table, '<i>Winzone coordinates in winmap</i>',
            7, 12, 2, 3);
        $self->addLabel($table, 'Top-left X (blocks)',
            7, 9, 3, 4);
        $self->addEntry($table, 'left', FALSE,
            9, 12, 3, 4);
        $self->addLabel($table, 'Top-left Y',
            7, 9, 4, 5);
        $self->addEntry($table, 'top', FALSE,
            9, 12, 4, 5);
        $self->addLabel($table, 'Bottom-right X',
            7, 9, 5, 6);
        $self->addEntry($table, 'right', FALSE,
            9, 12, 5, 6);
        $self->addLabel($table, 'Bottom-right Y',
            7, 9, 6, 7);
        $self->addEntry($table, 'bottom', FALSE,
            9, 12, 6, 7);
        $self->addLabel($table, 'Width (blocks)',
            7, 9, 7, 8);
        $self->addEntry($table, 'width', FALSE,
            9, 12, 7, 8);
        $self->addLabel($table, 'Height',
            7, 9, 8, 9);
        $self->addEntry($table, 'height', FALSE,
            9, 12, 8, 9);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Init', $self->notebook);

        # Create a temporary table object so we can access its IVs
        $package = $self->editObj->packageName;
        if ($package) {

            $tempObj = $package->new(-1, 'temp', 'temp', 'temp');
        }

        # Client file objects
        $self->addLabel($table, '<b>Initialisation settings</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 9,
            -1, 230);       # Fixed height

        # 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($table, 'Initialisation key/value',
                1, 2, 9, 10);
            @comboList = (sort {lc($a) cmp lc($b)} ($tempObj->ivKeys('initHash')));
            my $comboBox = $self->addComboBox($table, undef, \@comboList, '',
                TRUE,              # no 'undef' value allowed
                2, 6, 9, 10);

            my $entry = $self->addEntry($table, undef, TRUE,
                6, 10, 9, 10);

            my $button = $self->addButton(
                $table,
                'Use value',
                'Use the specified initialisation key with the value above',
                undef,
                10, 12, 9, 10);

            my $button2 = $self->addButton(
                $table,
                'Use the default setting instead',
                'Don\'t use the specified initialisation setting',
                undef,
                1, 6, 10, 11);

            my $button3 = $self->addButton(
                $table,
                'Use \'undef\'',
                'Use the specified initialisation key with an \'undef\' value',
                undef,
                6, 8, 10, 11);

            my $button4 = $self->addButton(
                $table,
                'Use TRUE',
                'Use the specified initialisation key with a TRUE value',
                undef,
                8, 9, 10, 11);

            my $button5 = $self->addButton(
                $table,
                'Use FALSE',
                'Use the specified initialisation key with a FALSE value',
                undef,
                10, 12, 10, 11);

            my $button6 = $self->addButton(
                $table,
                '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub initTab_refreshList {

        # Resets the simple list displayed by $self->file2Tab
        #
        # Expected arguments
        #   $slWidget       - The GA::Gtk::Simple::List
        #   $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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Workspace object</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Settings for this workspace object</i>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Object number',
            1, 3, 2, 3);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'System workspace number',
            1, 3, 3, 4);
        my $entry = $self->addEntry($table, undef, FALSE,
            3, 6, 3, 4);
        if ($self->editObj->wnckWorkspace) {
            $entry->set_text($self->editObj->wnckWorkspace->get_number());
        } else {
            $entry->set_text('(unknown)');
        }
        $self->addLabel($table, 'System workspace name',
            1, 3, 4, 5);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            3, 6, 4, 5);
        if ($self->editObj->wnckWorkspace) {
            $entry2->set_text($self->editObj->wnckWorkspace->get_name());
        } else {
            $entry2->set_text('(unknown)');
        }
        $self->addLabel($table, 'System screen number',
            1, 3, 5, 6);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            3, 6, 5, 6);
        if ($self->editObj->wnckScreen) {
            $entry3->set_text($self->editObj->wnckScreen->get_number());
        } else {
            $entry3->set_text('(unknown)');
        }

        $self->addLabel($table, 'Current width (pixels)',
            1, 3, 6, 7);
        $self->addEntry($table, 'currentWidth', FALSE,
            3, 6, 6, 7);
        $self->addLabel($table, 'Current height',
            1, 3, 7, 8);
        $self->addEntry($table, 'currentHeight', FALSE,
            3, 6, 7, 8);
        $self->addLabel($table, 'Default zonemap',
            1, 3, 8, 9);
        $self->addEntry($table, 'defaultZonemap', FALSE,
            3, 6, 8, 9);

        # Right column
        $self->addLabel($table, '<i>Panel sizes for this workspace (if detected)</i>',
            7, 12, 1, 2);
        $self->addLabel($table, 'Left',
            7, 9, 2, 3);
        $self->addEntry($table, 'panelLeftSize', FALSE,
            9, 12, 2, 3);
        $self->addLabel($table, 'Right',
            7, 9, 3, 4);
        $self->addEntry($table, 'panelRightSize', FALSE,
            9, 12, 3, 4);
        $self->addLabel($table, 'Top',
            7, 9, 4, 5);
        $self->addEntry($table, 'panelTopSize', FALSE,
            9, 12, 4, 5);
        $self->addLabel($table, 'Bottom',
            7, 9, 5, 6);
        $self->addEntry($table, 'panelBottomSize', FALSE,
            9, 12, 5, 6);

        $self->addLabel($table, '<i>Window controls sizes (if detected)</i>',
            7, 12, 6, 7);
        $self->addLabel($table, 'Left',
            7, 9, 7, 8);
        $self->addEntry($table, 'controlsLeftSize', FALSE,
            9, 12, 7, 8);
        $self->addLabel($table, 'Right',
            7, 9, 8, 9);
        $self->addEntry($table, 'controlsRightSize', FALSE,
            9, 12, 8, 9);
        $self->addLabel($table, 'Top',
            7, 9, 9, 10);
        $self->addEntry($table, 'controlsTopSize', FALSE,
            9, 12, 9, 10);
        $self->addLabel($table, 'Bottom',
            7, 9, 10, 11);
        $self->addEntry($table, 'controlsBottomSize', FALSE,
            9, 12, 10, 11);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Grids', $self->notebook);

        # Workspaces
        $self->addLabel($table, '<b>Workspaces grids</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<i>List of workspace grids on this workspace</i>',
            1, 6, 1, 2);
        $self->addCheckButton($table, 'gridEnableFlag', FALSE,
            6, 7, 1, 2, 0, 0.5);
        $self->addLabel($table,
            'Grids enabled on this workspace',
            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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);           # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Zonemap',
            1, 3, 10, 11);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('zonemapHash'));
        $existString = '(use existing zonemap)';
        my $combo = $self->addComboBox($table, undef, \@comboList, $existString,
            TRUE,               # No 'undef' value used
            3, 5, 10, 11);

        my $button = $self->addButton(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub gridsTab_refreshList {

        # Called by $self->gridTab to refresh the first GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Workspace grid</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Settings for this workspace grid</i>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Grid number',
            1, 3, 2, 3);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 2, 3);
        $self->addLabel($table, 'On workspace',
            1, 3, 3, 4);
        my $entry = $self->addEntry($table, undef, FALSE,
            3, 6, 3, 4);
        $entry->set_text($self->editObj->workspaceObj->number);
        $self->addLabel($table, 'Controlling session',
            1, 3, 4, 5);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            3, 6, 4, 5);
        if (! $self->editObj->owner) {
            $entry2->set_text('(shared)');
        } else {
            $entry2->set_text($self->editObj->owner->number);
        }
        $self->addLabel($table, 'Zonemap',
            1, 3, 5, 6);
        $self->addEntry($table, 'zonemap', FALSE,
            3, 6, 5, 6);

        $self->addLabel($table, '<i>Layers</i>',
            1, 6, 6, 7);
        $self->addLabel($table, 'Current layer',
            1, 3, 7, 8);
        $self->addEntry($table, 'currentLayer', FALSE,
            3, 6, 7, 8);
        $self->addLabel($table, 'Max layers',
            1, 3, 8, 9);
        $self->addEntry($table, 'maxLayers', FALSE,
            3, 6, 8, 9);

        # Right column
        $self->addLabel($table, '<i>Size of grid (in gridblocks)</i>',
            7, 12, 1, 2);
        $self->addLabel($table, 'Width',
            7, 9, 2, 3);
        $self->addEntry($table, 'widthBlocks', FALSE,
            9, 12, 2, 3);
        $self->addLabel($table, 'Height',
            7, 9, 3, 4);
        $self->addEntry($table, 'heightBlocks', FALSE,
            9, 12, 3, 4);

        $self->addLabel($table, '<i>Position of grid on workspace (pixels)</i>',
            7, 12, 4, 5);
        $self->addLabel($table, 'X',
            7, 9, 5, 6);
        $self->addEntry($table, 'xPosPixels', FALSE,
            9, 12, 5, 6);
        $self->addLabel($table, 'Y',
            7, 9, 6, 7);
        $self->addEntry($table, 'yPosPixels', FALSE,
            9, 12, 6, 7);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        my ($vBox, $table) = $self->addTab('_Zones', $self->notebook);

        # Zones
        $self->addLabel($table, '<b>Zones</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);           # Fixed height

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

        # Add a button
        my $button = $self->addButton(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub zonesTab_refreshList {

        # Called by $self->zonesTab to refresh the first GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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
        my ($vBox, $table) = $self->addTab('_Windows', $self->notebook);

        # Windows
        $self->addLabel($table, '<b>Windows</b>',
            0, 12, 0, 1);
        $self->addLabel($table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 300);           # Fixed height

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

        # Add buttons
        my $button = $self->addButton(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub windowsTab_refreshList {

        # Called by $self->windowsTab to refresh the first GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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
#       my ($vBox, $table) = $self->addTab('_General', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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
                'trackPosnFlag', 'track_current_room', 'icon_track_current_room',
                'paintAllRoomsFlag', 'paint_all', 'icon_paint_all',
                'drawOrnamentsFlag', 'draw_ornaments', 'icon_draw_ornaments',
                # ->settings2Tab
                '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,
                 # ->settings3Tab
                'allowModelScriptFlag', 'allow_model_scripts', undef,
                'allowRoomScriptFlag', 'allow_room_scripts', undef,
                 # ->settings4Tab
                # (none)
                # ->settings5Tab
                '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,
                # ->settings6Tab
                'autoOpenWinFlag', 'auto_open_win', undef,
                'pseudoWinFlag', 'pseudo_win', undef,
                'allowTrackAloneFlag', 'keep_following', undef,
                'setTwinOrnamentFlag', 'also_set_twin_exits', undef,
                'intelligentExitsFlag', 'intelligent_uncertain', undef,
                'autoCompareFlag', 'auto_compare_rooms', 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', '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 object viewer window, if it is open
            if ($self->session->viewerWin) {

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

            # Redraw maps in all Automapper windows using this world model
            $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 ($vBox, $table) = $self->addTab('_General', $self->notebook);

        # General properties
        $self->addLabel($table, '<b>General properties</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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(
            $table,
            '<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($table, 'Author',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'author', 'string', 1, 128,
            3, 12, 3, 4);
        $self->addLabel($table, 'Date',
            1, 3, 4, 5);
        $self->addEntryWithIcon($table, 'date', 'string', 1, 64,
            3, 6, 4, 5);
        $self->addLabel($table, 'Version',
            7, 9, 4, 5);
        $self->addEntryWithIcon($table, 'version', 'string', 1, 64,
            9, 12, 4, 5);
        $self->addLabel($table, 'Creation date',
            1, 3, 5, 6);
        $self->addEntry($table, 'modelCreationDate', FALSE,
            3, 6, 5, 6);
        $self->addLabel($table, $axmud::SCRIPT . ' Version',
            7, 9, 5, 6);
        $self->addEntry($table, 'modelCreationVersion', FALSE,
            9, 12, 5, 6);
        $self->addLabel($table, 'Description',
            1, 3, 6, 9);
        $self->addTextView($table, 'descripList', TRUE,
            3, 12, 6, 9,
            TRUE, FALSE, FALSE, FALSE,  # Treat as a list, don't remove empty lines or whitespace
            -1, 140);                   # Fixed height

        # Bottom row
        $self->addLabel(
            $table,
            '<i>Location of the mudlib (if installed on this machine) and file extension (if'
            . ' required)</i>',
            1, 12, 9, 10,
        );

        $self->addLabel($table, 'Mudlib path',
            1, 3, 10, 11);
        $self->addEntryWithIcon($table, 'mudlibPath', 'string', 0, undef,
            3, 12, 10, 11);
        $self->addLabel($table, 'File extension',
            1, 3, 11, 12);
        $self->addEntryWithIcon($table, 'mudlibExtension', 'string', 0, undef,
            3, 6, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Sizes', $self->notebook);

        # Default sizes
        $self->addLabel($table, '<b>Default sizes</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Default size of regionmaps, in gridblocks (use odd numbers; minimum value is'
            . ' 5)</i>',
            1, 12, 1, 2,
        );

        $self->addLabel($table, 'Width (x axis)',
            1, 3, 2, 3);
        $self->addEntryWithIcon(
            $table,
            'defaultGridWidthBlocks',
            'odd',
            5,
            $self->editObj->maxGridWidthBlocks,
            3, 6, 2, 3,
        );

        $self->addLabel($table, 'Maximum width',
            7, 9, 2, 3);
        $self->addEntry($table, 'maxGridWidthBlocks', FALSE,
            9, 12, 2, 3);

        $self->addLabel($table, 'Height (y axis)',
            1, 3, 3, 4);
        $self->addEntryWithIcon(
            $table,
            'defaultGridHeightBlocks',
            'odd',
            5,
            $self->editObj->maxGridHeightBlocks,
            3, 6, 3, 4,
        );

        $self->addLabel($table, 'Maximum height',
            7, 9, 3, 4);
        $self->addEntry($table, 'maxGridHeightBlocks', FALSE,
            9, 12, 3, 4);

        $self->addLabel(
            $table,
            '<i>Default size of a gridblock, in pixels (use odd numbers; minimum value is 11)</i>',
            1, 12, 4, 5,
        );

        $self->addLabel($table, 'Width',
            1, 3, 5, 6);
        $self->addEntryWithIcon(
            $table,
            'defaultBlockWidthPixels',
            'odd',
            11,
            $self->editObj->maxBlockWidthPixels,
            3, 6, 5, 6,
        );

        $self->addLabel($table, 'Maximum width',
            7, 9, 5, 6);
        $self->addEntry($table, 'maxBlockWidthPixels', FALSE,
            9, 12, 5, 6);

        $self->addLabel($table, 'Height',
            1, 3, 6, 7);
        $self->addEntryWithIcon(
            $table,
            'defaultBlockHeightPixels',
            'odd',
            11,
            $self->editObj->maxBlockHeightPixels,
            3, 6, 6, 7,
        );

        $self->addLabel($table, 'Maximum height',
            7, 9, 6, 7);
        $self->addEntry($table, 'maxBlockHeightPixels', FALSE,
            9, 12, 6, 7);

        $self->addLabel(
            $table,
            '<i>Default size of a room, in pixels (use odd numbers; minimum value is 5)</i>',
            1, 12, 7, 8,
        );

        $self->addLabel($table, 'Width',
            1, 3, 8, 9);
        $self->addEntryWithIcon(
            $table,
            'defaultRoomWidthPixels',
            'odd',
            5,
            $self->editObj->maxRoomWidthPixels,
            3, 6, 8, 9,
        );

        $self->addLabel($table, 'Maximum width',
            7, 9, 8, 9);
        $self->addEntry($table, 'maxRoomWidthPixels', FALSE,
            9, 12, 8, 9);

        $self->addLabel($table, 'Height',
            1, 3, 9, 10);
        $self->addEntryWithIcon(
            $table,
            'defaultRoomHeightPixels',
            'odd',
            5,
            $self->editObj->maxRoomHeightPixels,
            3, 6, 9, 10,
        );

        $self->addLabel($table, 'Maximum height',
            7, 8, 9, 10);
        $self->addEntry($table, 'maxRoomHeightPixels', FALSE,
            9, 12, 9, 10);

        $self->addLabel(
            $table,
            '<i>Default size of the regionmap, in pixels (use the \'save\' button to update these'
            . ' values)</i>',
            1, 12, 10, 11,
        );
        $self->addLabel($table, 'Width',
            1, 3, 11, 12);
        $self->addEntry($table, 'defaultMapWidthPixels', FALSE,
            3, 6, 11, 12);
        $self->addLabel($table, 'Height',
            7, 9, 11, 12);
        $self->addEntry($table, 'defaultMapHeightPixels', FALSE,
            9, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Model', $self->notebook);

        # 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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Models
        $self->addLabel($table, '<b>Models</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, '<i>World model</i>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Number of objects',
            1, 3, 2, 3);
        $self->addEntry($table, 'modelActualCount', FALSE,
            3, 6, 2, 3, 8, 8);
        $self->addLabel($table, 'Highest object #',
            1, 3, 3, 4);
        $self->addEntry($table, 'modelObjCount', FALSE,
            3, 6, 3, 4, 8, 8);
        $self->addLabel($table, 'Newest object #',
            1, 3, 4, 5);
        $self->addEntry($table, 'mostRecentNum', FALSE,
            3, 6, 4, 5, 8, 8);

        # Right column
        $self->addLabel($table, '<i>Exit model</i>',
            7, 12, 1, 2);
        $self->addLabel($table, 'Number of objects',
            7, 9, 2, 3);
        $self->addEntry($table, 'exitActualCount', FALSE,
            9, 12, 2, 3, 8, 8);
        $self->addLabel($table, 'Highest object #',
            7, 9, 3, 4);
        $self->addEntry($table, 'exitObjCount', FALSE,
            9, 12, 3, 4, 8, 8);
        $self->addLabel($table, 'Newest object #',
            7, 9, 4, 5);
        $self->addEntry($table, 'mostRecentExitNum', FALSE,
            9, 12, 4, 5, 8, 8);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub model2Tab {

        # model2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # List of regionmaps
        $self->addLabel($table, '<b>List of regionmaps</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 230);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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(
            $table,
            '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->addLabel($table, 'Automapper shows regions in reverse',
            1, 5, 11, 12);
        $self->addCheckButton($table, 'reverseRegionListFlag', TRUE,
            5, 6, 11, 12, 1, 0.5);

        $self->addLabel($table, '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($table, 'firstRegion', \@modList, '',
            FALSE,              # 'undef' value allowed
            9, 12, 11, 12);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub model2Tab_refreshList {

        # Called by $self->model2Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Default sizes
        $self->addLabel($table, '<b>List of character model objects</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub model3Tab_refreshList {

        # Called by $self->model3Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Default sizes
        $self->addLabel($table, '<b>List of minion strings</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);       # Fixed height

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

        # Add editing buttons
        $self->addLabel($table, 'Minion string',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 9, 8, 9);
        $self->addLabel($table, 'Own minion',
            9, 11, 8, 9);
        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            11, 12, 8, 9, 1, 0.5);
        $checkButton->set_active(TRUE);     # Default - minion belongs to user

        my $button = $self->addButton(
            $table,
            '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($table, 'Model number',
            3, 6, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'int', 1, undef,
            6, 9, 9, 10);

        my $button2 = $self->addButton(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub model4Tab_refreshList {

        # Called by $self->model4Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Default sizes
        $self->addLabel($table, '<b>List of room tags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add editing buttons
        my $button = $self->addButton($table, '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub model5Tab_refreshList {

        # Called by $self->model5Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Default sizes
        $self->addLabel($table, '<b>List of teleport destinations</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 200);      # Fixed height

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

        # Add entries/comboboxes for adding new variables
        $self->addLabel($table, 'Room name',
            1, 3, 8, 9);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 6, 8, 9);

        $self->addLabel($table, 'Equivalent world command',
            1, 3, 9, 10);
        my $entry2 = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        # Add standard editing buttons to the simple list
        my $button = $self->addSimpleListButtons_hashIV(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('S_ettings', $self->notebook);

        # Add tabs to the inner notebook
        $self->settings1Tab($innerNotebook);
        $self->settings2Tab($innerNotebook);
        $self->settings3Tab($innerNotebook);
        $self->settings4Tab($innerNotebook);
        $self->settings5Tab($innerNotebook);
        $self->settings6Tab($innerNotebook);

        return 1;
    }

    sub settings1Tab {

        # Settings1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->settings1Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Set up some hashes, so that entry boxes in this tab can display text, not just a number
        %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_flag'     => '\'room_flag\' - Room flag text',
            'visit_count'   => '\'visit_count\' - Character visits',
            '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',
            'vnum'          => '\'vnum\' - Room vnum',
        );

        # Settings
        $self->addLabel($table, '<b>Settings</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, '<u>Window components</u>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Show menu bar',
            1, 5, 2, 3);
        $self->addCheckButton($table, 'showMenuBarFlag', TRUE,
            5, 6, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Show toolbar',
            1, 5, 3, 4);
        $self->addCheckButton($table, 'showToolbarFlag', TRUE,
            5, 6, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Show region list',
            1, 5, 4, 5);
        $self->addCheckButton($table, 'showTreeViewFlag', TRUE,
            5, 6, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Show map',
            1, 5, 5, 6);
        $self->addCheckButton($table, 'showCanvasFlag', TRUE,
            5, 6, 5, 6, 1, 0.5);

        $self->addLabel($table, '<u>Tracking</u>',
            1, 6, 6, 7);
        $self->addLabel($table, 'Track position',
            1, 5, 7, 8);
        $self->addCheckButton($table, 'trackPosnFlag', TRUE,
            5, 6, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Tracking sensitivity',
            1, 4, 8, 9);
        $self->addEntry($table, 'trackingSensitivity', FALSE,
            4, 6, 8, 9, 4, 4);

        $self->addLabel($table, '<u>Painter</u>',
            1, 6, 9, 10);
        $self->addLabel($table, 'Paint existing rooms',
            1, 5, 10, 11);
        $self->addCheckButton($table, 'paintAllRoomsFlag', FALSE,
            5, 6, 10, 11, 1, 0.5);

        # Right column
        $self->addLabel($table, '<u>Drawing modes</u>',
            7, 12, 1, 2);

        $self->addLabel($table, 'Current room mode',
            7, 9, 2, 3);
        my $entry = $self->addEntry($table, undef, FALSE,
            9, 12, 2, 3);
        $entry->set_text($currentModeHash{$self->editObj->currentRoomMode});

        $self->addLabel($table, 'Room interior mode',
            7, 9, 3, 4);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            9, 12, 3, 4);
        $entry2->set_text($interiorModeHash{$self->editObj->roomInteriorMode});

        $self->addLabel($table, 'Draw exit mode',
            7, 9, 4, 5);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            9, 12, 4, 5);
        $entry3->set_text($exitModeHash{$self->editObj->drawExitMode});

        $self->addLabel($table, 'Draw exit ornaments',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'drawOrnamentsFlag', TRUE,
            11, 12, 5, 6, 1, 0.5);

        $self->addLabel($table, '<u>Map font</u>',
            7, 12, 6, 7);

        $self->addLabel($table, 'Font',
            7, 9, 7, 8);
        my $entry4 = $self->addEntry($table, undef, FALSE,
            9, 10, 7, 8);
        $entry4->set_text($self->editObj->mapFont);
        my $button = $self->addButton($table, 'Modify', 'Change the automapper font', undef,
            10, 12, 7, 8);
        $button->signal_connect('clicked' => sub {

            my $newFont = $self->showFontSelectionDialogue('Automapper window font');

            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($table, '<u>Label alignment</u>',
            7, 12, 8, 9);

        $self->addLabel($table, 'Labels alignored horizontally',
            7, 11, 9, 10);
        $self->addCheckButton($table, 'mapLabelAlignXFlag', FALSE,
            11, 12, 9, 10, 1, 0.5);
        $self->addLabel($table, 'Labels aligned vertically',
            7, 11, 10, 11);
        $self->addCheckButton($table, 'mapLabelAlignYFlag', FALSE,
            11, 12, 10, 11, 1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings2Tab {

        # Settings2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # Settings (cont.)
        $self->addLabel($table, '<b>Settings (cont.)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, '<u>Room matching</u>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Match room titles',
            1, 5, 2, 3);
        $self->addCheckButton($table, 'matchTitleFlag', TRUE,
            5, 6, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Match (verbose) descriptions',
            1, 5, 3, 4);
        $self->addCheckButton($table, 'matchDescripFlag', TRUE,
            5, 6, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Characters to match (0 - match whole)',
            1, 4, 4, 5);
        $self->addEntryWithIcon($table, 'matchDescripCharCount', 'int', 0, undef,
            4, 6, 4, 5, 4, 4);
        $self->addLabel($table, 'Match exits',
            1, 5, 5, 6);
        $self->addCheckButton($table, 'matchExitFlag', TRUE,
            5, 6, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Analyse verbose descriptions',
            1, 5, 6, 7);
        $self->addCheckButton($table, 'analyseDescripFlag', TRUE,
            5, 6, 6, 7, 1, 0.5);
        $self->addLabel($table, 'Match room source code path',
            1, 5, 7, 8);
        $self->addCheckButton($table, 'matchSourceFlag', TRUE,
            5, 6, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Match world\'s room vnum',
            1, 5, 8, 9);
        $self->addCheckButton($table, 'matchVNumFlag', TRUE,
            5, 6, 8, 9, 1, 0.5);

        # Right column
        $self->addLabel($table, '<u>Room updating</u>',
            7, 12, 1, 2);
        $self->addLabel($table, 'Update room titles',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'updateTitleFlag', TRUE,
            11, 12, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Update room descriptions',
            7, 11, 3, 4);
        $self->addCheckButton($table, 'updateDescripFlag', TRUE,
            11, 12, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Update exits',
            7, 11, 4, 5);
        $self->addCheckButton($table, 'updateExitFlag', TRUE,
            11, 12, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Update exit ornaments using exit states',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'updateOrnamentFlag', TRUE,
            11, 12, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Update room source code path',
            7, 11, 6, 7);
        $self->addCheckButton($table, 'updateSourceFlag', TRUE,
            11, 12, 6, 7, 1, 0.5);
        $self->addLabel($table, 'Update world\'s room vnum',
            7, 11, 7, 8);
        $self->addCheckButton($table, 'updateVNumFlag', TRUE,
            11, 12, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Update room commands',
            7, 11, 8, 9);
        $self->addCheckButton($table, 'updateRoomCmdFlag', TRUE,
            11, 12, 8, 9, 1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings3Tab {

        # Settings3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        # Settings (cont.)
        $self->addLabel($table, '<b>Settings (cont.)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel(
            $table,
            '<u>' . $axmud::BASIC_NAME . ' scripts run when entering new rooms</u>',
            1, 10, 1, 2);
        $self->addTextView($table, 'newRoomScriptList', TRUE,
            1, 10, 2, 10,
            TRUE, TRUE, TRUE, FALSE,    # Treat as a list, remove empty lines, remove whitespace
            -1, 250);                   # Fixed height

        $self->addLabel(
            $table,
            'Allow scripts applying to all rooms in the world model above (including the scripts'
            . ' listed above)',
            2, 12, 10, 11);
        $self->addCheckButton($table, 'allowModelScriptFlag', TRUE,
            1, 2, 10, 11, 1, 0.5);
        $self->addLabel($table, 'Allow invididual room scripts (which run before the scripts'
            . ' listed above)',
            2, 12, 11, 12);
        $self->addCheckButton($table, 'allowRoomScriptFlag', TRUE,
            1, 2, 11, 12, 1, 0.5);

        # Right column
        $self->addLabel(
            $table,
            '<u>' . $axmud::BASIC_NAME . ' scripts run when entering existing rooms</u>',
            10, 12, 1, 2);
        $self->addTextView($table, 'arriveScriptList', TRUE,
            10, 12, 2, 10,
            TRUE, TRUE, TRUE, FALSE,    # Treat as a list, remove empty lines, remove whitespace
            -1, 250);                   # Fixed height

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings4Tab {

        # Settings4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        # Settings (cont.)
        $self->addLabel($table, '<b>Settings (cont.)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, '<u>Searches (0 - no limit)</u>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Max objects searched',
            1, 3, 2, 3);
        $self->addEntryWithIcon($table, 'searchMaxObjects', 'int', 0, undef,
            3, 6, 2, 3, 4, 4);
        $self->addLabel($table, 'Maximum matches',
            1, 3, 3, 4);
        $self->addEntryWithIcon($table, 'searchMaxMatches', 'int', 0, undef,
            3, 6, 3, 4, 4, 4);
        $self->addLabel($table, 'Select matching rooms',
            1, 5, 4, 5);
        $self->addCheckButton($table, 'searchSelectRoomsFlag', TRUE,
            5, 6, 4, 5, 1, 0.5);

        $self->addLabel($table, '<u>Locate room (0 - no limit)</u>',
            1, 6, 5, 6);
        $self->addLabel($table, 'Max rooms compared',
            1, 3, 6, 7);
        $self->addEntryWithIcon($table, 'locateMaxObjects', 'int', 0, undef,
            3, 6, 6, 7, 8, 8);
        $self->addLabel($table, '<u>Locate room after random exit</u>',
            1, 6, 7, 8);
        $self->addLabel($table, 'Locate in region',
            1, 5, 8, 9);
        $self->addCheckButton($table, 'locateRandomInRegionFlag', TRUE,
            5, 6, 8, 9, 1, 0.5);
        $self->addLabel($table, 'Locate anywhere',
            1, 5, 9, 10);
        $self->addCheckButton($table, 'locateRandomAnywhereFlag', TRUE,
            5, 6, 9, 10, 1, 0.5);

        # Right column
        $self->addLabel($table, '<u>Exit lengths</u>',
            7, 12, 1, 2);
        $self->addLabel($table, 'Horizontal exit length (blocks)',
            7, 9, 2, 3);
        $self->addEntryWithIcon(
            $table,
            'horizontalExitLengthBlocks',
            'int', 1, $self->editObj->maxExitLengthBlocks,
            9, 12, 2, 3, 2, 2);
        $self->addLabel($table, 'Vertical exit length (blocks)',
            7, 9, 3, 4);
        $self->addEntryWithIcon(
            $table,
            'verticalExitLengthBlocks',
            'int', 1, $self->editObj->maxExitLengthBlocks,
            9, 12, 3, 4, 2, 2);
        $self->addLabel($table, 'Maximum exit length (blocks)',
            7, 9, 4, 5);
        $self->addEntry($table, 'maxExitLengthBlocks', FALSE,
            9, 12, 4, 5, 4, 4);

        $self->addLabel($table, '<u>Show \'Working...\' dialogue</u>',
            7, 12, 5, 6);
        $self->addLabel($table, 'Draw objects (100+)',
            7, 9, 6, 7);
        $self->addEntryWithIcon($table, 'drawPauseNum', 'int', 100, undef,
            9, 12, 6, 7, 8, 8);
        $self->addLabel($table, 'Calculate region paths (10+)',
            7, 9, 7, 8);
        $self->addEntryWithIcon($table, 'recalculatePauseNum', 'int', 10, undef,
            9, 12, 7, 8, 8, 8);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings5Tab {

        # Settings5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 . '->settings5Tab', @_);
        }

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        # Settings (cont.)
        $self->addLabel($table, '<b>Settings (cont.)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, '<u>Assisted moves</u>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Assisted moves enabled',
            1, 5, 2, 3);
        my $checkButton = $self->addCheckButton($table, undef, TRUE,
            5, 6, 2, 3, 1, 0.5);
        $checkButton->set_active($self->editObj->assistedMovesFlag);
        # (->signal_connect appears below)

        $self->addLabel($table, 'Break down doors',
            1, 5, 3, 4);
        $self->addCheckButton($table, 'assistedBreakFlag', TRUE,
            5, 6, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Pick locks',
            1, 5, 4, 5);
        $self->addCheckButton($table, 'assistedPickFlag', TRUE,
            5, 6, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Unlock doors',
            1, 5, 5, 6);
        $self->addCheckButton($table, 'assistedUnlockFlag', TRUE,
            5, 6, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Open doors',
            1, 5, 6, 7);
        $self->addCheckButton($table, 'assistedOpenFlag', TRUE,
            5, 6, 6, 7, 1, 0.5);
        $self->addLabel($table, 'Close doors',
            1, 5, 7, 8);
        $self->addCheckButton($table, 'assistedCloseFlag', TRUE,
            5, 6, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Lock doors',
            1, 5, 8, 9);
        $self->addCheckButton($table, 'assistedLockFlag', TRUE,
            5, 6, 8, 9, 1, 0.5);

        $self->addLabel($table, '<u>Protected moves</u>',
            1, 6, 9, 10);

        $self->addLabel($table, 'Protected moves enabled',
            1, 5, 10, 11);
        my $checkButton2 = $self->addCheckButton($table, 'protectedMovesFlag', TRUE,
            5, 6, 10, 11, 1, 0.5);
        # ->signal_connect appears below
        if (! $self->editObj->assistedMovesFlag) {

            $checkButton2->set_state('insensitive');
        }

        $self->addLabel($table, 'Cancel commands when move overruled',
            1, 5, 11, 12);
        my $checkButton3 = $self->addCheckButton($table, 'superProtectedMovesFlag', TRUE,
            5, 6, 11, 12, 1, 0.5);
        if (! $self->editObj->assistedMovesFlag) {

            $checkButton3->set_state('insensitive');
        }

        # Right column
        $self->addLabel($table, '<u>Crafty moves</u>',
            7, 12, 1, 2);

        $self->addLabel($table, 'Crafty moves enabled',
            7, 11, 2, 3);
        my $checkButton4 = $self->addCheckButton($table, 'craftyMovesFlag', TRUE,
            11, 12, 2, 3, 1, 0.5);
        if ($self->editObj->protectedMovesFlag) {

            $checkButton4->set_state('insensitive');
        }

        $self->addLabel($table, '<u>Pathfinding</u>',
            7, 12, 3, 4);
        $self->addLabel($table, 'Avoid hazards',
            7, 11, 4, 5);
        $self->addCheckButton($table, 'avoidHazardsFlag', TRUE,
            11, 12, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Apply post-processing to paths',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'postProcessingFlag', TRUE,
            11, 12, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Double-click pathfinding',
            7, 11, 6, 7);
        $self->addCheckButton($table, 'quickPathFindFlag', TRUE,
            11, 12, 6, 7, 1, 0.5);
        $self->addLabel($table, 'Maximum steps in path (0 - no limit)',
            7, 10, 7, 8);
        $self->addEntryWithIcon($table, 'pathFindStepLimit', 'int', 0, undef,
            10, 12, 7, 8, 8, 8);
        $self->addLabel($table, '<u>Checked directions</u>',
            7, 12, 8, 9);
        $self->addLabel($table, 'Collect checked directions',
            7, 11, 9, 10);
        $self->addCheckButton($table, 'collectCheckedDirsFlag', TRUE,
            11, 12, 9, 10, 1, 0.5);
        $self->addLabel($table, 'Draw checked directions',
            7, 11, 10, 11);
        $self->addCheckButton($table, 'drawCheckedDirsFlag', TRUE,
            11, 12, 10, 11, 1, 0.5);
        $self->addLabel($table, 'Checkable directions to count',
            7, 12, 11, 12);

        @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($table, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            7, 11, 12, 13);
        $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub settings6Tab {

        # Settings6 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _6', $innerNotebook);

        # Settings (cont.)
        $self->addLabel($table, '<b>Settings (cont.)</b>',
            0, 12, 0, 1);

        # Left column
        $self->addLabel($table, '<u>Miscellaneous flags</u>',
            1, 6, 1, 2);
        $self->addLabel($table, 'Open window automatically',
            1, 5, 2, 3);
        $self->addCheckButton($table, 'autoOpenWinFlag', TRUE,
            5, 6, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Open as pseudo-window (if possible)',
            1, 5, 3, 4);
        $self->addCheckButton($table, 'pseudoWinFlag', TRUE,
            5, 6, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Keep following character after closing',
            1, 5, 4, 5);
        $self->addCheckButton($table, 'allowTrackAloneFlag', TRUE,
            5, 6, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Set twin exit ornaments',
            1, 5, 5, 6);
        $self->addCheckButton($table, 'setTwinOrnamentFlag', TRUE,
            5, 6, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Auto-complete uncertain exits',
            1, 5, 6, 7);
        $self->addCheckButton($table, 'autocompleteExitsFlag', TRUE,
            5, 6, 6, 7, 1, 0.5);
        $self->addLabel($table, 'Intelligent uncertain exits',
            1, 5, 7, 8);
        $self->addCheckButton($table, 'intelligentExitsFlag', TRUE,
            5, 6, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Auto-compare new rooms',
            1, 5, 8, 9);
        $self->addCheckButton($table, 'autoCompareFlag', TRUE,
            5, 6, 8, 9, 1, 0.5);
        $self->addLabel($table, 'Draw new exits for follow anchors',
            1, 5, 9, 10);
        $self->addCheckButton($table, 'followAnchorFlag', TRUE,
            5, 6, 9, 10, 1, 0.5);
        $self->addLabel($table, 'Auto-set tags for region exits',
            1, 5, 10, 11);
        $self->addCheckButton($table, 'updateExitTagFlag', TRUE,
            5, 6, 10, 11, 1, 0.5);
        $self->addLabel($table, 'Show all 18 primary directions in dialogues',
            1, 5, 11, 12);
        $self->addCheckButton($table, 'showAllPrimaryFlag', TRUE,
            5, 6, 11, 12, 1, 0.5);

        # Right column
        $self->addLabel($table, 'Capitalise room tags',
            7, 11, 2, 3);
        $self->addCheckButton($table, 'capitalisedRoomTagFlag', TRUE,
            11, 12, 2, 3, 1, 0.5);
        $self->addLabel($table, 'Show tooltips',
            7, 11, 3, 4);
        $self->addCheckButton($table, 'showTooltipsFlag', TRUE,
            11, 12, 3, 4, 1, 0.5);
        $self->addLabel($table, 'Show room notes in tooltips',
            7, 11, 4, 5);
        $self->addCheckButton($table, 'showNotesFlag', TRUE,
            11, 12, 4, 5, 1, 0.5);
        $self->addLabel($table, 'Show explanation when lost',
            7, 11, 5, 6);
        $self->addCheckButton($table, 'explainGetLostFlag', TRUE,
            11, 12, 5, 6, 1, 0.5);
        $self->addLabel($table, 'Update mode disabled',
            7, 11, 6, 7);
        $self->addCheckButton($table, 'disableUpdateModeFlag', TRUE,
            11, 12, 6, 7, 1, 0.5);
        $self->addLabel($table, 'Count character visits',
            7, 11, 7, 8);
        $self->addCheckButton($table, 'countVisitsFlag', TRUE,
            11, 12, 7, 8, 1, 0.5);
        $self->addLabel($table, 'Draw room echos',
            7, 11, 8, 9);
        $self->addCheckButton($table, 'drawRoomEchoFlag', TRUE,
            11, 12, 8, 9, 1, 0.5);
        $self->addLabel($table, 'Draw bent broken exits',
            7, 11, 9, 10);
        $self->addCheckButton($table, 'drawBentExitsFlag', TRUE,
            11, 12, 9, 10, 1, 0.5);
        $self->addLabel($table, 'Use CTRL+C for \'move rooms to click\'',
            7, 11, 10, 11);
        $self->addCheckButton($table, 'allowCtrlCopyFlag', TRUE,
            11, 12, 10, 11, 1, 0.5);

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Colours', $self->notebook);

        # Add tabs to the inner notebook
        $self->colours1Tab($innerNotebook);
        $self->colours2Tab($innerNotebook);
        $self->colours3Tab($innerNotebook);
        $self->colours4Tab($innerNotebook);
        $self->colours5Tab($innerNotebook);

        return 1;
    }

    sub colours1Tab {

        # Colours1 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        $self->addLabel($table, '<b>Map colours</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Colours used by the map, and their default values</i>',
            1, 12, 1, 2);

        $self->coloursTab_addRow(
            $table,
            2,
            'backgroundColour',
            'defaultBackgroundColour',
            'background',
        );

        $self->coloursTab_addRow(
            $table,
            3,
            'noBackgroundColour',
            'defaultNoBackgroundColour',
            'empty map',
        );

        $self->coloursTab_addRow(
            $table,
            4,
            'roomColour',
            'defaultRoomColour',
            'room',
        );

        $self->coloursTab_addRow(
            $table,
            5,
            'roomTextColour',
            'defaultRoomTextColour',
            'room interior text',
        );

        $self->coloursTab_addRow(
            $table,
            6,
            'selectBoxColour',
            'defaultSelectBoxColour',
            'selection box',
        );

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table      - The Gtk2::Table for this tab
        #   $row        - The number of the row in the Gtk2::Table displayed in this tab
        #   $iv         - The IV used, e.g. 'backgroundColour'
        #   $defaultIV  - The default colour used to set this IV, e.g. 'defaultBackgroundColour'
        #   $labelName  - Which label to use, e.g. 'background'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $table, $row, $iv, $defaultIV, $labelName, $check) = @_;

        # Local variables
        my ($rgbColour, $rgbDefault);

        # Check for improper arguments
        if (
            ! defined $table || ! 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->editObj->$defaultIV;

        # Colour
        $self->addLabel($table, ucfirst($labelName),
            1, 3, $row, ($row + 1));

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

        my $entry = $self->addEntry($table, $iv, FALSE,
            4, 6, $row, ($row + 1), 7, 7);

        my $button = $self->addButton($table, '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($table, '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($table, $rgbDefault, undef,
            9, 10, $row, ($row + 1));

        my $entry2 = $self->addEntry($table, $defaultIV, FALSE,
            10, 12, $row, ($row + 1), 7, 7);

        return 1;
    }

    sub colours2Tab {

        # Colours2 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        $self->addLabel($table, '<b>Map colours (cont.)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Room border colours used by the map, and their default values'
            . ' (see also next tab)</i>',
            1, 12, 1, 2,
        );

        $self->coloursTab_addRow(
            $table,
            2,
            'borderColour',
            'defaultBorderColour',
            'room border',
        );

        $self->coloursTab_addRow(
            $table,
            3,
            'currentBorderColour',
            'defaultCurrentBorderColour',
            'current room (\'update\' mode)',
        );

        $self->coloursTab_addRow(
            $table,
            4,
            'currentFollowBorderColour',
            'defaultCurrentFollowBorderColour',
            'current room (\'follow\' mode)',
        );

        $self->coloursTab_addRow(
            $table,
            5,
            'currentWaitBorderColour',
            'defaultCurrentWaitBorderColour',
            'current room (\'wait\' mode)',
        );

        $self->coloursTab_addRow(
            $table,
            6,
            'currentSelectBorderColour',
            'defaultCurrentSelectBorderColour',
            'current and selected room',
        );

        $self->coloursTab_addRow(
            $table,
            7,
            'lostBorderColour',
            'defaultLostBorderColour',
            'lost room border',
        );

        $self->coloursTab_addRow(
            $table,
            8,
            'lostSelectBorderColour',
            'defaultLostSelectBorderColour',
            'selected lost room border',
        );

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub colours3Tab {

        # Colours3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _3', $innerNotebook);

        $self->addLabel($table, '<b>Map colours (cont.)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Room border colours used by the map, and their default values (see also previous'
            . ' tab)</i>',
            1, 12, 1, 2,
        );

        $self->coloursTab_addRow(
            $table,
            2,
            'ghostBorderColour',
            'defaultGhostBorderColour',
            'ghost room border',
        );

        $self->coloursTab_addRow(
            $table,
            3,
            'ghostSelectBorderColour',
            'defaultGhostSelectBorderColour',
            'ghost and selected room border',
        );

        $self->coloursTab_addRow(
            $table,
            4,
            'selectBorderColour',
            'defaultSelectBorderColour',
            'selected room border',
        );

        $self->coloursTab_addRow(
            $table,
            5,
            'roomAboveColour',
            'defaultRoomAboveColour',
            'room echo (from above)',
        );

        $self->coloursTab_addRow(
            $table,
            6,
            'roomBelowColour',
            'defaultRoomBelowColour',
            'room echo (from below)',
        );

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub colours4Tab {

        # Colours4 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _4', $innerNotebook);

        $self->addLabel($table, '<b>Map colours (cont.)</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<i>Exit colours used by the map, and their default values</i>',
            1, 12, 1, 2);

        $self->coloursTab_addRow(
            $table,
            2,
            'exitColour',
            'defaultExitColour',
            'exit',
        );

        $self->coloursTab_addRow(
            $table,
            3,
            'selectExitColour',
            'defaultSelectExitColour',
            'selected exit',
        );

        $self->coloursTab_addRow(
            $table,
            4,
            'selectExitTwinColour',
            'defaultSelectExitTwinColour',
            'selected exit\'s twin',
        );

        $self->coloursTab_addRow(
            $table,
            5,
            'selectExitShadowColour',
            'defaultSelectExitShadowColour',
            'selected exit\'s shadow exit',
        );

        $self->coloursTab_addRow(
            $table,
            6,
            'randomExitColour',
            'defaultRandomExitColour',
            'random exit (2nd colour)',
        );

        $self->coloursTab_addRow(
            $table,
            7,
            'impassableExitColour',
            'defaultImpassableExitColour',
            'impassable exit',
        );

        $self->coloursTab_addRow(
            $table,
            8,
            'checkedDirColour',
            'defaultCheckedDirColour',
            'checked dir',
        );

        $self->coloursTab_addRow(
            $table,
            9,
            'dragExitColour',
            'defaultDragExitColour',
            'draggable exit',
        );

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub colours5Tab {

        # Colours5 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _5', $innerNotebook);

        $self->addLabel($table, '<b>Map colours (cont.)</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>Room tag / room guild / exit tag colours used by the map, and their default'
            . ' values</i>',
            1, 12, 1, 2,
        );

        $self->coloursTab_addRow(
            $table,
            2,
            'roomTagColour',
            'defaultRoomTagColour',
            'room tag',
        );

        $self->coloursTab_addRow(
            $table,
            3,
            'selectRoomTagColour',
            'defaultSelectRoomTagColour',
            'selected room tag',
        );

        $self->coloursTab_addRow(
            $table,
            4,
            'roomGuildColour',
            'defaultRoomGuildColour',
            'room guild',
        );

        $self->coloursTab_addRow(
            $table,
            5,
            'selectRoomGuildColour',
            'defaultSelectRoomGuildColour',
            'selected room guild',
        );

        $self->coloursTab_addRow(
            $table,
            6,
            'exitTagColour',
            'defaultExitTagColour',
            'exit tag',
        );

        $self->coloursTab_addRow(
            $table,
            7,
            'selectExitTagColour',
            'defaultSelectExitTagColour',
            'selected exit tag',
        );

        $self->coloursTab_addRow(
            $table,
            8,
            'mapLabelColour',
            'defaultMapLabelColour',
            'label (failsafe colour)',
        );

        $self->coloursTab_addRow(
            $table,
            9,
            'selectMapLabelColour',
            'defaultSelectMapLabelColour',
            'selected label',
        );

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Label styles', $self->notebook);

        # Label styles
        $self->addLabel($table, '<b>Label styles</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 11,
            -1, 300);      # Fixed height

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

        # Add editing widgets
        $self->addLabel($table, 'Style name',
            1, 2, 11, 12);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, 16,
            2, 4, 11, 12);

        my $button = $self->addButton($table,
            '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 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($table, '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 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($table, '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 show the style
                $self->stylesTab_refreshList($slWidget, (scalar @columnList / 2));
            }
        });

        my $button4 = $self->addButton($table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub stylesTab_refreshList {

        # Resets the simple list displayed by $self->stylesTab
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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 ($vBox, $innerNotebook) = $self->addInnerNotebookTab('_Room flags', $self->notebook);

        # Add tabs to the inner notebook
        $self->roomFlags1Tab($innerNotebook);
        $self->roomFlags2Tab($innerNotebook);

        $self->roomFlags3Tab(
            $innerNotebook,
           3,
            '<b>Painting by room titles</b>',
            '<i>List of patterns matching a room\'s title, and the corresponding room flag used'
            . ' when the painter is on</i>',
            'paintFromTitleHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            4,
            '<b>Painting by room descriptions</b>',
            '<i>List of patterns matching a room\'s description, and the corresponding room flag'
            . ' used when the painter is on</i>',
            'paintFromDescripHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            5,
            '<b>Painting by room exits</b>',
            '<i>List of patterns matching one of the room\'s exits, and the corresponding room flag'
            . ' used when the painter is on</i>',
            'paintFromExitHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            6,
            '<b>Painting by room contents</b>',
            '<i>List of patterns matching an object in a room, and the corresponding room flag used'
            . ' when the painter is on</i>',
            'paintFromObjHash',
        );

        $self->roomFlags3Tab(
            $innerNotebook,
            7,
            '<b>Painting by room commands</b>',
            '<i>List of patterns matching a room command, and the corresponding room flag used when'
            . ' the painter is on</i>',
            'paintFromRoomCmdHash',
        );

        return 1;
    }

    sub roomFlags1Tab {

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

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

        # Local variables
        my (
            $sortMode,
            @columnList,
        );

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

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

        # Tab setup
        my ($vBox, $table) = $self->addTab('Page _1', $innerNotebook);

        # Room flags
        $self->addLabel($table, '<b>Room flags</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<i>List of flags that mark a room as being of a particular type</i>',
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Flag', 'text',
            'Custom', 'bool',
            'Abbrev', 'text',
            'Priority', 'int',
            'Filter', 'text',
            'Colour', 'text',
            'Description', 'text',
        );

        my $slWidget = $self->addSimpleList($table, undef, \@columnList,
            1, 12, 2, 9,
            -1, 200);       # Fixed height

        # 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($table, undef, FALSE,
            1, 4, 9, 10,
            16, 16);

        $self->addLabel($table, 'Colour',
            4, 5, 9, 10);
        my ($frame, $canvas, $canvasObj) = $self->addSimpleCanvas($table, undef, undef,
            5, 6, 9, 10);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            6, 7, 9, 10,
            7, 7);
        my $button = $self->addButton($table, 'Change', 'Change this colour', undef,
            7, 8, 9, 10,
            TRUE);              # Irreversible

        my $button2 = $self->addButton($table, 'Use default:', 'Use the default colour', undef,
            8, 10, 9, 10,
            TRUE);              # Irreversible
        my ($frame2, $canvas2, $canvasObj2) = $self->addSimpleCanvas($table, undef, undef,
            10, 11, 9, 10);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            11, 12, 9, 10,
            7, 7);

        my $button3 = $self->addButton(
            $table,
            'Move up',
            'Move this flag up the priority list',
            undef,
            1, 2, 10, 11,
            TRUE,               # Irreversible
        );

        my $button4 = $self->addButton(
            $table,
            'Move down',
            'Move this flag down the priority list',
            undef,
            2, 4, 10, 11,
            TRUE,               # Irreversible
        );

        my $button5 = $self->addButton(
            $table,
            'Move to top',
            'Move this flag down the priority list',
            undef,
            4, 6, 10, 11,
            TRUE,               # Irreversible
        );

        my $button6 = $self->addButton(
            $table,
            'Move to bottom',
            'Move this flag down the priority list',
            undef,
            6, 8, 10, 11,
            TRUE,               # Irreversible
        );

        my $button7 = $self->addButton(
            $table,
            'Sort by priority',
            'Sort room flags by priority',
            undef,
            8, 10, 10, 11,
        );

        my $button8 = $self->addButton(
            $table,
            'Sort by filter',
            'Sort room flags by filter',
            undef,
            10, 12, 10, 11,
        );

        my $button9 = $self->addButton(
            $table,
            'Add custom flag',
            'Add a custom room flag',
            undef,
            1, 5, 11, 12,
        );

        my $button10 = $self->addButton(
            $table,
            'Delete custom flag',
            'Delete a custom room flag',
            undef,
            5, 8, 11, 12,
        );
        $button10->set_sensitive(FALSE);

        my $button11 = $self->addButton(
            $table,
            'Sort alphabetically',
            'Sort room flags alphabetically',
            undef,
            8, 10, 11, 12,
        );

        my $button12 = $self->addButton(
            $table,
            'Restore defaults',
            'Restore default flag colours/priorities',
            undef,
            10, 12, 11, 12,
            TRUE,               # Irreversible
        );

        # 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 chaneg
                $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->session->worldModelObj->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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub roomFlags1Tab_refreshList {

        # Called by $self->roomFlags1Tab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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,
        );

        # 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);
        }

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

            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 Gtk2::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 ($vBox, $table) = $self->addTab('Page _2', $innerNotebook);

        # MSDP terrain types
        $self->addLabel($table, '<b>MSDP terrain types</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, 'roomTerrainHash', \@columnList,
            1, 12, 2, 9,
            -1, 200);       # Fixed height

        # Add editing widgets
        $self->addLabel($table, 'Terrain type',
            1, 3, 9, 10);
        @comboList = sort {lc($a) cmp lc($b)} ($self->editObj->ivKeys('roomTerrainInitHash'));
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            3, 6, 9, 10);

        $self->addLabel($table, 'Room flag category (filter)',
            1, 4, 10, 11);
        @comboList2 = $axmud::CLIENT->constRoomFilterList;
        my $combo2 = $self->addComboBox($table, undef, \@comboList2, '',
            TRUE,              # 'undef' value not used
            4, 6, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($table, 'Room flag',
            6, 7, 10, 11);
        @comboList3 = $self->editObj->getRoomFlagsInFilter($combo2->get_active_text());
        my $combo3 = $self->addComboBox($table, undef, \@comboList3, '',
            TRUE,              # 'undef' value not used
            7, 10, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($table, '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($table, $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(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub roomFlags3Tab {

        # RoomFlags3 tab
        #
        # Expected arguments
        #   $innerNotebook  - The Gtk2::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 ($vBox, $table) = $self->addTab('Page _' . $number, $innerNotebook);

        # Painting by room titles
        $self->addLabel($table, $title,
            0, 12, 0, 1);
        $self->addLabel($table, $descrip,
            1, 12, 1, 2,
        );

        # Add a simple list
        @columnList = (
            'Pattern', 'text',
            'Room flag', 'text',
        );

        my $slWidget = $self->addSimpleList($table, $iv, \@columnList,
            1, 12, 2, 9,
            -1, 200);       # Fixed height

        # Add editing widgets
        $self->addLabel($table, 'Pattern',
            1, 3, 9, 10);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
            3, 12, 9, 10);

        $self->addLabel($table, 'Room flag category (filter)',
            1, 4, 10, 11);
        @comboList = $axmud::CLIENT->constRoomFilterList;
        my $combo = $self->addComboBox($table, undef, \@comboList, '',
            TRUE,              # 'undef' value not used
            4, 6, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($table, 'Room flag',
            6, 7, 10, 11);
        @comboList2 = $self->editObj->getRoomFlagsInFilter($combo->get_active_text());
        my $combo2 = $self->addComboBox($table, undef, \@comboList2, '',
            TRUE,              # 'undef' value not used
            7, 10, 10, 11);
        # ->signal_connect appears below

        $self->addLabel($table, '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($table, $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(
            $table,
            $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
        $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('L_ight', $self->notebook);

        # Light status list
        $self->addLabel($table, '<b>Ligt status list</b>',
            0, 12, 0, 1);
        $self->addLabel(
            $table,
            '<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($table, undef, \@columnList,
            1, 12, 2, 10,
            -1, 270);       # Fixed height

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

        # Add entry boxes and buttons
        $self->addLabel($table, 'Light status',
            1, 3, 10, 11);
        my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, 16,
            3, 6, 10, 11, 16, 16);
        my $button = $self->addButton($table, '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($table,
            '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($table, '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($table,
            '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(
            $table,
            '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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

    sub lightTab_refreshList {

        # Called by $self->lightTab to refresh the GA::Gtk::Simple::List
        #
        # Expected arguments
        #   $slWidget   - The GA::Gtk::Simple::List
        #   $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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

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

#   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 table size
        my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # 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->{entryStartX} = undef;
        $self->{entryStartY} = undef;
        $self->{entryStopX} = undef;
        $self->{entryStopY} = 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->{drawingArea2} = undef;
        $self->{editButton} = undef;
        $self->{deleteButton} = undef;

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

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

        # Tab complete
        $vBox->pack_start($table, 0, 0, 0);

        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
        #   $table  -  The Gtk2::Table which has already been created for this tab
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

#       # Tab setup (already created by the calling function)
#       my ($vBox, $table) = $self->addTab('_Name', $self->notebook);

        # Left column
        $self->addLabel($table, '<b>Zonemap settings</b>',
            0, 6, 0, 2);
        $self->addLabel($table, 'Zonemap name',
            1, 3, 2, 4);
        $self->addEntry($table, 'name', FALSE,
            3, 6, 2, 4, 16, 16);

        # Right column
        $self->addLabel($table, 'Zonemap is full',
            7, 11, 2, 4);
        $self->addCheckButton($table, 'fullFlag', FALSE,
            11, 12, 2, 4, 1, 0.5);
        $self->addLabel($table, 'Zonemap is temporary',
            7, 11, 4, 6);
        $self->addCheckButton($table, 'tempFlag', FALSE,
            11, 12, 4, 6, 1, 0.5);

#       # Tab complete (handled by the calling function)
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Zone models', $self->notebook);

        # We're going to draw a grid full of pretty colours, using a pixmap and a Gtk2::DrawingArea
        #   object

        ########
        # Part 1 - draw the 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 around 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 area, containing the main grid - a 60x60 grid containing zones
        #   $self->zoneModelsTab_refreshGrid    - Draws the grid
        #   $self->zoneModelsTab_getMouseClick  - Responds to mouse clicks on the area
        #   $self->zoneModelsTab_getMouseMotion - Responds to mouse motion over the are
        $widthRequest = $self->borderX + ($self->gridSize * $self->cellWidth) + 30;
        $heightRequest = $self->borderY + ($self->gridSize * $self->cellHeight) + 50;

        my $drawingAreaObj = $self->addDrawingArea(
            $table,
            '',
            'zoneModelsTab_refreshGrid',
            'zoneModelsTab_getMouseClick',
            'zoneModelsTab_getMouseMotion',
            FALSE, FALSE,                       # No scrolling
            0, 8, 0, 12,                        # Position in table
            $widthRequest, $heightRequest,      # Requested size of viewport
        );

        ########
        # Part 2 - widgets for adding new zones

        $self->addLabel($table, '<b>Add a new zone model</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($table,
            undef, 'Use 5-block clicks', undef, undef, TRUE,
            8, 12, 1, 2);

        ($radioGroup, $radioButton2) = $self->addRadioButton($table,
            $radioGroup, 'Use 1-block clicks', undef, undef, TRUE,
            8, 12, 2, 3);

        $self->addLabel($table, 'Top-left corner',
            8, 10, 3, 4);
        my $entry = $self->addEntry($table, undef, FALSE,
            10, 11, 3, 4, 3, 3);
        my $entry2 = $self->addEntry($table, undef, FALSE,
            11, 12, 3, 4, 3, 3);

        $self->addLabel($table, 'Bottom-right corner',
            8, 10, 4, 5);
        my $entry3 = $self->addEntry($table, undef, FALSE,
            10, 11, 4, 5, 3, 3);
        my $entry4 = $self->addEntry($table, undef, FALSE,
            11, 12, 4, 5, 3, 3);

        # Save these entry boxes as IVs, so they can be updated as the mouse moves over the drawing
        #   area
        $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($table,
            'Add zone model', 'Create a zone model using the selected area', undef,
            8, 10, 5, 6,
            TRUE,           # Irreversible
        );
        my $button2 = $self->addButton($table,
            'Cancel', 'Don\'t create a zone model using the selected area', undef,
            10, 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($table, '<b>Zone model actions</b>',
            8, 12, 6, 7);

        # When clicking on an existing zone, the zone model's number is displayed...
        $self->addLabel($table, 'Selected model #',
            8, 10, 7, 8);
        my $entry5 = $self->addEntry($table, undef, FALSE,
            10, 11, 7, 8, 3, 3);
        $self->ivPoke('entryCurrentZone', $entry5);

        # ...as is its colour
        my $drawingAreaObj2 = $self->addDrawingArea($table, '',
            undef, undef, undef,    # No function references
            FALSE, FALSE,           # No scrolling
            11, 12, 7, 8,           # Position in table
            15, 15,                 # Width/height request
        );

        $self->ivPoke('drawingArea2', $drawingAreaObj2);

        # Editing buttons
        my $button3;
        if ($axmud::CLIENT->checkCurrentZonemap($self->editObj->name)) {

            $button3 = $self->addButton($table, 'View...', 'View the selected zone model', undef,
                8, 10, 8, 9);

        } else {

            $button3 = $self->addButton($table, 'Edit...', 'Edit the selected zone model', undef,
                8, 10, 8, 9);
        }

        my $button4 = $self->addButton($table, 'Delete', 'Delete the selected zone model', undef,
            10, 12, 8, 9,
            TRUE,           # Irreversible
        );
        my $button5 = $self->addButton($table,
            'Dump list', 'Display the list of zone models in the \'main\' window', undef,
            8, 10, 9, 10);
        my $button6 = $self->addButton($table, 'Refresh grid', 'Refresh the zonemap grid', undef,
            10, 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,
            );

            # 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($drawingAreaObj);
        });

        # Cancel button
        $button2->signal_connect('clicked' => sub {

            # Re-draw the grid and all existing zones, and return to step 1
            $self->zoneModelsTab_resetMouseStep($drawingAreaObj);
        });

        # 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 zones, and return to step 1
            $self->zoneModelsTab_resetMouseStep($drawingAreaObj);
        });

        # 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 zones, and return to step 1
                    $self->zoneModelsTab_resetMouseStep($drawingAreaObj);
                }
            }
        });

        # 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($drawingAreaObj);
        });

        # 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($drawingAreaObj);

            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
        $vBox->pack_start($table, 0, 0, 0);

        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 area, or because they clicked the
        #   'cancel' button, or because they changed from 1-block clicks to 5-block clicks (or
        #   vice-versa)
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

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

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

        # Re-draw the grid and all existing zones, removing the selected area
        $self->zoneModelsTab_refreshGrid($drawingAreaObj);

        # 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('');

        $self->drawingArea2->drawRectangle(
            [0, 0, 50, 50],
            'white',
            TRUE,                   # Rectangle filled in
        );

        # Move back to the first step
        $self->ivPoke('mouseStep', 1);

        return 1;
    }

    sub zoneModelsTab_refreshGrid {

        # Called by $self->addDrawingArea (inherited from GA::Generic::Win), following a call from
        #   $self->zoneModelsTab
        # Draws (or re-draws) the grid, and fills in parts of the grid to show existing zone models
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

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

        # Local variables
        my (
            $count,
            @modelList, @sortedList, @colourList,
        );

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

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

        # Draw a white rectangle, filled in, to draw over anything that already exists
        $drawingAreaObj->drawRectangle(
            [
                $self->borderX,
                $self->borderY,
                # (+1 to remove the bottom and right black lines)
                ($self->gridSize * $self->cellWidth) + 1,
                ($self->gridSize * $self->cellHeight) + 1,
            ],
            'white',
            TRUE,           # Rectangle filled in
        );

        # Draw a 60x60 grey grid

        # Draw vertical lines
        for (
            my $x = $self->borderX;
            $x <= ($self->borderX + ($self->gridSize * $self->cellWidth));
            $x += $self->cellWidth
        ) {
            $drawingAreaObj->drawLine(
                [
                    $x,
                    $self->borderY,
                    $x,
                    ($self->borderY + ($self->gridSize * $self->cellHeight))
                ],
                'gray',
            );
        }

        # Draw horizontal lines
        for (
            my $y = $self->borderY;
            $y <= ($self->borderY + ($self->gridSize * $self->cellHeight));
            $y += $self->cellHeight
        ) {
            $drawingAreaObj->drawLine(
                [
                    $self->borderX,
                    $y,
                    ($self->borderX + ($self->gridSize * $self->cellWidth)),
                    $y
                ],
                'gray',
            );
        }

        # Draw a black grid, overlaid on the grey grid, on every 5th line

        # Draw vertical lines
        for (
            my $x = $self->borderX;
            $x <= ($self->borderX + ($self->gridSize * $self->cellWidth));
            $x += ($self->cellWidth * 5)
        ) {
            $drawingAreaObj->drawLine(
                [
                    $x,
                    $self->borderY,
                    $x,
                    ($self->borderY + ($self->gridSize * $self->cellHeight))
                ],
                'black',
            );
        }

        # Draw horizontal lines
        for (
            my $y = $self->borderY;
            $y <= ($self->borderY + ($self->gridSize * $self->cellHeight));
            $y += ($self->cellHeight * 5)
        ) {
            $drawingAreaObj->drawLine(
                [
                    $self->borderX,
                    $y,
                    ($self->borderX + ($self->gridSize * $self->cellWidth)),
                    $y
                ],
                'black',
            );
        }

        # Draw each existing zone model in turn
        @modelList = $self->editObj->ivKeys('modelHash');
        # Sort the list numerically
        @sortedList = sort {$a <=> $b} (@modelList);
        # Import a list of 32 colours in roughly rainbow order
        @colourList = $axmud::CLIENT->constRainbowColourList;

        # Draw each zone in turn, using a different colour for each
        foreach my $modelNum (@sortedList) {

            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(
                $drawingAreaObj,
                $modelObj->left,
                $modelObj->top,
                $modelObj->right,
                $modelObj->bottom,
                $colour,
            );
        }

        # Operation complete
        return 1;
    }

    sub zoneModelsTab_getMouseClick {

        # Called by $self->addDrawingArea (inherited from GA::Generic::Win), whenever the user
        #   clicks on the drawing area
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $xPos, $yPos    - The coordinates on the drawing area at which the user clicked
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $drawingAreaObj, $xPos, $yPos, $check) = @_;

        # Local variables
        my (
            $xGrid, $yGrid, $zoneModelObj, $number, $posn,
            @colourList, @sortedList,
        );

        # Check for improper arguments
        if (! defined $drawingAreaObj || ! defined $xPos || ! defined $yPos || defined $check) {

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

        # Convert $xPos and $yPos - coordinates in the drawing area - 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($drawingAreaObj);

                # 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);
                }

                $self->drawingArea2->drawRectangle(
                    [0, 0, 50, 50],
                    $colourList[$posn],
                    TRUE,           # Rectangle filled in
                );

                # 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($drawingAreaObj);

                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($drawingAreaObj, $xGrid, $yGrid, 'pink');

                } else {

                    # Mark a 5x5 group of gridblocks pink
                    $self->zoneModelsTab_fillZone(
                        $drawingAreaObj,
                        $self->startX,
                        $self->startY,
                        $self->startX + 4,
                        $self->startY + 4,
                        'pink',
                    );
                }

                # If the number of a zone that's been clicked on is displayed, remove it
                $self->entryCurrentZone->set_text('');

                $self->drawingArea2->drawRectangle(
                    [0, 0, 50, 50],
                    'white',
                    TRUE,       # Rectangle filled in
                );

                # 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(
                    $drawingAreaObj,
                    $self->startX,
                    $self->startY,
                    $self->stopX,
                    $self->stopY,
                    'pink',
                );

                # 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($drawingAreaObj);
            }
        }

        return 1;
    }

    sub zoneModelsTab_getMouseMotion {

        # Called by $self->addDrawingArea (inherited from GA::Generic::Win), whenever the user moves
        #   the mouse pointer over the drawing area
        # Updates the tab's entry boxes and buttons
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $xPos, $yPos    - The current coordiantes of the mouse over the drawing area
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $drawingAreaObj, $xPos, $yPos, $check) = @_;

        # Local variables
        my ($xGrid, $yGrid);

        # Check for improper arguments
        if (! defined $drawingAreaObj || ! defined $xPos || ! defined $yPos || defined $check) {

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

        # Convert $xPos and $yPos - coordinates in the drawing area - 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_fillBlock {

        # Called by various functions of this 'edit' window object
        # Fills in a single block of the grid in a single colour
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $xPos, $yPos    - The coordinates of the gridblock to be filled in
        #   $colour         - The colour, e.g. 'red'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my ($self, $drawingAreaObj, $xPos, $yPos, $colour, $check) = @_;

        # Check for improper arguments
        if (
            ! defined $drawingAreaObj || ! defined $xPos || ! defined $yPos || ! defined $colour
            || defined $check
        ) {
            return $axmud::CLIENT->writeImproper(
                $self->_objClass . '->zoneModelsTab_fillBlock',
                @_,
            );
        }

        # Fill the square at grid coordinates $xPos, $yPos
        $drawingAreaObj->drawRectangle(
            [
                $self->borderX + ($xPos * $self->cellWidth) + 1,
                $self->borderY + ($yPos * $self->cellHeight) + 1,
                $self->cellWidth - 1,
                $self->cellHeight - 1,
            ],
            $colour,
            TRUE,           # Rectangle filled in
        );

        return 1
    }

    sub zoneModelsTab_fillZone {

        # Called by various functions of this 'edit' window object
        # Fills in an area of the grid in a single colour, representing a particular zone model
        #
        # Expected arguments
        #   $drawingAreaObj - The GA::Obj::DrawingArea in use
        #   $startX, $startY, $stopX, $stopY
        #                   - The area of the grid to fill in
        #   $colour         - The colour, e.g. 'red'
        #
        # Return values
        #   'undef' on improper arguments
        #   1 otherwise

        my (
            $self, $drawingAreaObj, $startX, $startY, $stopX, $stopY, $colour,
            $check
        ) = @_;

        # Check for improper arguments
        if (
            ! defined $drawingAreaObj || ! defined $startX || ! defined $startY || ! defined $stopX
            || ! defined $stopY || ! defined $colour || defined $check
        ) {
            return $axmud::CLIENT->writeImproper($self->_objClass . '->zoneModelsTab_fillZone', @_);
        }

        # Fill the area bounded by $startX, $startY, $stopX, $stopY
        for (my $xPos = $startX; $xPos <= $stopX; $xPos++) {

            for (my $yPos = $startY; $yPos <= $stopY; $yPos++) {

                # Fill the square at grid coordinates $xPos, $yPos
                $drawingAreaObj->drawRectangle(
                    [
                        $self->borderX + ($xPos * $self->cellWidth) + 1,
                        $self->borderY + ($yPos * $self->cellHeight) + 1,
                        $self->cellWidth - 1,
                        $self->cellHeight - 1,
                    ],
                    $colour,
                    TRUE,       # Rectangle filled in
                );
            }
        }

        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 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 drawingArea2
        { $_[0]->{drawingArea2} }
    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)
        #   $tooltips   - A Gtk2::Tooltips object for the buttons (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 $tooltips || defined $check) {

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

        if ($axmud::CLIENT->checkCurrentZonemap($self->editObj->zonemapObj->name)) {
            return $self->enableSingleButton($hBox, $tooltips);
        } else {
            return $self->SUPER::enableButtons($hBox, $tooltips);
        }
    }

#   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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # 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
#       $vBox->pack_start($table, 0, 0, 0);

        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 ($vBox, $table) = $self->addTab('_Number', $self->notebook);

        # (Need just a little extra space to make everything fit)
        $table->set_col_spacings($self->spacingPixels - 1);
        $table->set_row_spacings($self->spacingPixels - 1);

        # Can't edit zone models in use
        if ($axmud::CLIENT->checkCurrentZonemap($self->editObj->zonemapObj->name)) {

            $currentFlag = TRUE;
        }

        # Left column
        if ($currentFlag) {

            $self->addLabel(
                $table,
                '<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($table, '<b>Zone model settings</b>',
                0, 12, 0, 1);
        }

        $self->addLabel($table, 'Zone model number',
            1, 3, 1, 2);
        $self->addEntry($table, 'number', FALSE,
            3, 6, 1, 2, 4, 4);
        $self->addLabel($table, 'Parent zonemap',
            1, 3, 2, 3);
        my $entry = $self->addEntry($table, undef, FALSE,
            3, 6, 2, 3, 16, 16);
        $entry->set_text($self->editObj->zonemapObj->name);

        $self->addLabel($table, 'Default winmap (\'main\' windows)',
            1, 3, 3, 4);
        @comboList = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('winmapHash'));
        my $combo = $self->addComboBox($table, 'defaultEnabledWinmap', \@comboList, '',
            FALSE,              # 'undef' value used
            3, 6, 3, 4);

        $self->addLabel($table, '...when workspace grids disactivated',
            1, 3, 4, 5);
        my $combo2 = $self->addComboBox($table, 'defaultDisabledWinmap', \@comboList, '',
            FALSE,              # 'undef' value used
            3, 6, 4, 5);

        $self->addLabel($table, 'Default winmap (other windows)',
            1, 3, 5, 6);
        my $combo3 = $self->addComboBox($table, 'defaultInternalWinmap', \@comboList, '',
            FALSE,              # 'undef' value used
            3, 6, 5, 6);

        $self->addLabel($table, '<i>Coordinates in parent zonemap:</i>',
            1, 6, 6, 7);
        $self->addLabel($table, 'Top-left X',
            1, 3, 7, 8);
        $self->addEntry($table, 'left', FALSE,
            3, 6, 7, 8, 4, 4);
        $self->addLabel($table, 'Top-left Y',
            1, 3, 8, 9);
        $self->addEntry($table, 'top', FALSE,
            3, 6, 8, 9, 4, 4);
        $self->addLabel($table, 'Bottom-right X',
            1, 3, 9, 10);
        $self->addEntry($table, 'right', FALSE,
            3, 6, 9, 10, 4, 4);
        $self->addLabel($table, 'Bottom-right Y',
            1, 3, 10, 11);
        $self->addEntry($table, 'bottom', FALSE,
            3, 6, 10, 11, 4, 4);
        $self->addLabel($table, 'Width (blocks)',
            1, 3, 11, 12);
        $self->addEntry($table, 'width', FALSE,
            3, 6, 11, 12, 4, 4);
        $self->addLabel($table, 'Height (blocks)',
            1, 3, 12, 13);
        $self->addEntry($table, 'height', FALSE,
            3, 6, 12, 13, 4, 4);

        # Right column
        $self->addLabel($table, 'Reserved windows (see next page)',
            7, 11, 1, 2);
        my $checkButton = $self->addCheckButton($table, 'reservedFlag', TRUE,
            11, 12, 1, 2, 1, 0.5);
        $self->addLabel($table, 'Allow multiple layers',
            7, 11, 2, 3);
        my $checkButton2 = $self->addCheckButton($table, 'multipleLayerFlag', TRUE,
            11, 12, 2, 3, 1, 0.5);

        $self->addLabel($table, 'Optional owner ID)',
            7, 9, 3, 4);
        $self->addEntryWithIconButton($table, 'ownerString', 'string', 0, undef,
            9, 12, 3, 4, 8, 8);

        $self->addLabel($table, 'Start corner',
            7, 9, 4, 5);
        @comboList2 = ('top_left', 'top_right', 'bottom_left', 'bottom_right');
        my $combo4 = $self->addComboBox($table, 'startCorner', \@comboList2, '',
            TRUE,               # No 'undef' value used
            9, 12, 4, 5);

        $self->addLabel($table, 'Zone orientation',
            7, 9, 5, 6);
        @comboList3 = ('horizontal', 'vertical');
        my $combo5 = $self->addComboBox($table, 'orientation', \@comboList3, '',
            TRUE,               # No 'undef' value used
            9, 12, 5, 6);

        $self->addLabel($table, '<i>Maximum area settings (0 = no max):</i>',
            7, 12, 6, 7);
        $self->addLabel($table, 'Max areas in zone',
            7, 9, 7, 8);
        my $entry2 = $self->addEntryWithIcon($table, 'areaMax', 'int', 0, undef,
            9, 12, 7, 8, 4, 4);
        $self->addLabel($table, 'Max areas in layer',
            7, 9, 8, 9);
        my $entry3 = $self->addEntryWithIcon($table, 'visibleAreaMax', 'int', 0, undef,
            9, 12, 8, 9, 4, 4);

        $self->addLabel($table, '<i>For unlimited areas, default size (0-60):</i>',
            7, 12, 9, 10);
        $self->addLabel($table, 'Area width (blocks)',
            7, 9, 10, 11);
        my $entry4 = $self->addEntryWithIcon($table, 'defaultAreaWidth', 'int', 0, 60,
            9, 12, 10, 11, 4, 4);
        $self->addLabel($table, 'Area height (blocks)',
            7, 9, 11, 12);
        my $entry5 = $self->addEntryWithIcon($table, '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
        $vBox->pack_start($table, 0, 0, 0);

        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
        my ($vBox, $table) = $self->addTab('_Reserved windows', $self->notebook);

        # Set a combo title now so we don't accidentally use it
        $comboTitle = 'Select a task:';

        # Reserved windows
        $self->addLabel($table, '<b>Reserved windows</b>',
            0, 12, 0, 1);
        $self->addLabel($table, '<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($table, undef, \@columnList,
            1, 12, 2, 8,
            -1, 240);      # Fixed height

        # 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($table, 'Window type',
                1, 3, 8, 9);

            @comboList = ('main', 'map', 'protocol', 'fixed', 'custom', 'external');
            my $combo = $self->addComboBox($table, undef, \@comboList, '',
                TRUE,               # No 'undef' value used
                3, 6, 8, 9);

            $self->addLabel($table, 'Window name',
                1, 3, 9, 10);
            my $entry = $self->addEntryWithIcon($table, undef, 'string', 1, undef,
                3, 6, 9, 10);
            $self->addLabel($table, '(or) task name',
                6, 9, 9, 10);

            @comboList2 = sort {lc($a) cmp lc($b)} ($axmud::CLIENT->ivKeys('taskPackageHash'));
            my $combo2 = $self->addComboBox($table, undef, \@comboList2, $comboTitle,
                FALSE,              # 'undef' value allowed
                9, 12, 9, 10);

            # Add standard editing buttons to the simple list
            my $button = $self->addSimpleListButtons_hashIV(
                $table,
                $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
        $vBox->pack_start($table, 0, 0, 0);

        return 1;
    }

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

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

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

# Package must return a true value
1
