package GCE::MapEditor; =head1 NAME GCE::MapEditor - the map editing widget =cut use Gtk2; use Gtk2::Gdk::Keysyms; use Gtk2::SimpleMenu; use Crossfire; use Crossfire::Map; use Crossfire::MapWidget; use GCE::AttrEdit; use GCE::Util; use Glib::Object::Subclass Gtk2::Window; use strict; sub build_menu { my ($self) = @_; my $menu_tree = [ _File => { item_type => '', children => [ "_Save" => { callback => sub { $self->save_map }, accelerator => 'S' }, "Save As" => { callback => sub { $self->save_map_as }, }, "_Map Properties" => { callback => sub { $self->open_map_prop }, accelerator => "P" }, "_Map Resize" => { callback => sub { $self->open_resize_map }, accelerator => "R" }, "Close" => { callback => sub { $self->destroy }, }, ] }, _Edit => { item_type => '', children => [ "_Undo" => { callback => sub { $self->undo }, accelerator => "Z" }, "_Redo" => { callback => sub { $self->redo }, accelerator => "Y" }, ] }, _Go => { item_type => '', children => [ "_Up" => { callback => sub { $self->follow ('u') }, accelerator => "U" }, "_Down" => { callback => sub { $self->follow ('d') }, accelerator => "D" }, "_Right" => { callback => sub { $self->follow ('r') }, accelerator => "R" }, "_Left" => { callback => sub { $self->follow ('l') }, accelerator => "L" }, ] }, _Help => { item_type => '', children => [ _Manual => { callback => sub { $::MAINWIN->show_help_window }, accelerator => "H" }, ] }, ]; my $men = Gtk2::SimpleMenu->new ( menu_tree => $menu_tree, default_callback => \&default_cb, ); for ( [i => 'pick'], [p => 'place'], [e => 'erase'], [s => 'select'], [l => 'eval'], [x => 'connectexit'], [f => 'followexit'] ) { my $tool = $_->[1]; $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible', sub { $::MAINWIN->set_edit_tool ($tool) }); } $self->add_accel_group ($men->{accel_group}); return $men->{widget}; } sub set_edit_tool { my ($self, $tool) = @_; $self->{etool} = $tool; if ($self->ea->special_arrow) { $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow)); } } sub ea { my ($self) = @_; $self->{ea_alt} || $self->{etool}; } sub INIT_INSTANCE { my ($self) = @_; $self->set_title ('gce - map editor'); $self->add (my $vb = Gtk2::VBox->new); $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0); $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0); $map->signal_connect_after (key_press_event => sub { my ($map, $event) = @_; my $kv = $event->keyval; my $ret = 0; my ($x, $y) = $map->coord ($map->get_pointer); for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }], [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }], [c => sub { $::MAINWIN->{edit_collection}{select}->copy }], [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }], [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }], ) { if ($kv == $Gtk2::Gdk::Keysyms{$_->[0]}) { $_->[1]->(); $ret = 1; } } if ($self->ea->special_arrow) { $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow)); } $ret }); $map->signal_connect_after (key_release_event => sub { my ($map, $event) = @_; my $ret = 0; if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L} or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L}) { delete $self->{ea_alt}; $ret = 1; } if ($self->ea->special_arrow) { $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow)); } else { # FIXME: Get the original cursor and insert it here $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR')); } $ret }); $map->signal_connect_after (button_press_event => sub { my ($map, $event) = @_; if ((not $self->{draw_mode}) and $event->button == 1) { my ($x, $y) = $map->coord ($event->x, $event->y); my $ea = $self->ea; $ea->begin ($map, $x, $y, $self) if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height}; $self->{draw_mode} = [$x, $y]; $ea->want_cursor or $map->disable_tooltip; return 1; } 0 }); $map->signal_connect_after (motion_notify_event => sub { my ($map, $event) = @_; $self->{draw_mode} or return; my $ea = $self->ea; my ($X, $Y) = @{$self->{draw_mode}}[0,1]; my ($x, $y) = $map->coord ($map->get_pointer); while ($x != $X || $y != $Y) { $X++ if $X < $x; $X-- if $X > $x; $Y++ if $Y < $y; $Y-- if $Y > $y; $ea->edit ($map, $X, $Y, $self) if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height}; } @{$self->{draw_mode}}[0,1] = ($X, $Y); 1 }); $map->signal_connect_after (button_release_event => sub { my ($map, $event) = @_; if ($self->{draw_mode} and $event->button == 1) { my ($x, $y) = $map->coord ($map->get_pointer); my $ea = $self->ea; $ea->end ($map, $x, $y, $self); delete $self->{draw_mode}; $ea->want_cursor or $map->enable_tooltip; return 1; } 0 }); } # FIXME: Fix the automatic update of the attribute editor! and also the stack view! sub undo { my ($self) = @_; my $map = $self->{map}; # the Crossfire::MapWidget $map->{undo_stack_pos} or return; $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]); } sub redo { my ($self) = @_; my $map = $self->{map}; # the Crossfire::MapWidget $map->{undo_stack} and $map->{undo_stack_pos} < @{$map->{undo_stack}} or return; $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]); } sub open_map { my ($self, $path, $key) = @_; $self->{mapkey} = $key; if (ref $path) { $self->{map}->set_map ($path); } else { $self->{path} = $path; # print "OPENMAP $path\n"; $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path); require Data::Dumper; # print "FOO:" .Data::Dumper::Dumper ($m) . "\n"; } } sub save_map { my ($self) = @_; if ($self->{path}) { $self->{map}{map}->write_file ($self->{path}); quick_msg ($self, "saved to $self->{path}"); } else { $self->save_map_as; } } sub save_map_as { my ($self) = @_; my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path}); if ('ok' eq $fc->run) { $::MAINWIN->{fc_last_folder} = $fc->get_current_folder; $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++; $self->{map}{map}->write_file ($self->{path} = $fc->get_filename); quick_msg ($self, "saved to $self->{path}"); } $fc->destroy; } sub _add_prop_entry { my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_; my $edwid; if ($type eq 'string') { $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1); $edwid = Gtk2::Entry->new; $edwid->set_text ($self->{map}{map}{info}{$key}); $edwid->signal_connect (changed => sub { $self->{map}{map}{info}{$key} = $_[0]->get_text; if ($changecb) { $changecb->($_[0]->get_text); } }); $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1); } elsif ($type eq 'button') { $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1); $b->signal_connect (clicked => ($changecb || sub {})); } elsif ($type eq 'label') { $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1); $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key}); $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1); } elsif ($type eq 'check') { $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1); $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1); $lbl->set_active ($self->{map}{map}{info}{$key}); $lbl->signal_connect (toggled => sub { my ($lbl) = @_; $self->{map}{map}{info}{$key} = $lbl->get_active * 1; ($changecb || sub {})->($lbl->get_active); }); } elsif ($type eq 'sep') { $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1); } else { $edwid = Gtk2::Label->new ("FOO"); } } sub open_resize_map { my ($self) = @_; my $w = Gtk2::Window->new ('toplevel'); $w->set_default_size (250, 150); $w->add (my $sw = Gtk2::ScrolledWindow->new); $sw->add_with_viewport (my $v = Gtk2::VBox->new); $sw->set_policy ('automatic', 'automatic'); $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0); my $i = 0; for ( [qw/width Width string/], [qw/height Height string/], [qw/save Save button/, sub { $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height}); $self->{map}->invalidate_all; $w->destroy; } ], ) { $self->_add_prop_entry ($t, $i++, @$_); } $w->show_all; } sub follow { my ($self, $dir) = @_; my %dir_to_path = ( u => 'tile_path_1', d => 'tile_path_3', r => 'tile_path_2', l => 'tile_path_4', ); defined $dir_to_path{$dir} or return; my $map = $self->{map}{map}{info}{$dir_to_path{$dir}} or return; $map = map2abs ($map, $self); $::MAINWIN->open_map_editor ($map); } sub open_map_prop { my ($self) = @_; my $w = Gtk2::Window->new ('toplevel'); $w->set_default_size (500, 500); $w->add (my $sw = Gtk2::ScrolledWindow->new); $sw->add_with_viewport (my $v = Gtk2::VBox->new); $sw->set_policy ('automatic', 'automatic'); $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0); my $i = 0; for ( [qw/name Name string/], [qw/region Region string/], [qw/enter_x Enter-x string/], [qw/enter_y Enter-y string/], [qw/reset_timeout Reset-timeout string/], [qw/swap_time Swap-timeout string/], [qw/x x sep/], [qw/difficulty Difficulty string/], [qw/windspeed Windspeed string/], [qw/pressure Pressure string/], [qw/humid Humid string/], [qw/temp Temp string/], [qw/darkness Darkness string/], [qw/sky Sky string/], [qw/winddir Winddir string/], [qw/x x sep/], [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }], [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }], [qw/x x sep/], [qw/msg Text text/], [qw/maplore Maplore text/], [qw/outdoor Outdoor check/], [qw/unique Unique check/], [qw/fixed_resettime Fixed-resettime check/], [qw/x x sep/], [qw/tile_path_1 Northpath string/], [qw/tile_path_2 Eastpath string/], [qw/tile_path_3 Southpath string/], [qw/tile_path_4 Westpath string/], [qw/tile_path_5 Toppath string/], [qw/tile_path_6 Bottompath string/], ) { $self->_add_prop_entry ($t, $i++, @$_); } $w->show_all; } =head1 AUTHOR Marc Lehmann http://home.schmorp.de/ Robin Redeker http://www.ta-sa.org/ =cut 1;