ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
(Generate patch)

Comparing deliantra/gde/GCE/MapEditor.pm (file contents):
Revision 1.5 by root, Sun Feb 12 04:50:29 2006 UTC vs.
Revision 1.29 by elmex, Mon Mar 20 04:11:41 2006 UTC

13use Crossfire; 13use Crossfire;
14use Crossfire::Map; 14use Crossfire::Map;
15use Crossfire::MapWidget; 15use Crossfire::MapWidget;
16 16
17use GCE::AttrEdit; 17use GCE::AttrEdit;
18use GCE::Util;
18 19
19use Glib::Object::Subclass 20use Glib::Object::Subclass
20 Gtk2::VBox, 21 Gtk2::Window;
21 ;
22 22
23use strict; 23use strict;
24 24
25sub build_menu {
26 my ($self) = @_;
27
28 my $menu_tree = [
29 _File => {
30 item_type => '<Branch>',
31 children => [
32 "_Save" => {
33 callback => sub { $self->save_map },
34 accelerator => '<ctrl>S'
35 },
36 "Save As" => {
37 callback => sub { $self->save_map_as },
38 },
39 "Close" => {
40 callback => sub { $self->delete; 1 },
41 },
42 ]
43 },
44 _Edit => {
45 item_type => '<Branch>',
46 children => [
47 "_Undo" => {
48 callback => sub { $self->undo },
49 accelerator => "<ctrl>Z"
50 },
51 "_Redo" => {
52 callback => sub { $self->redo },
53 accelerator => "<ctrl>Y"
54 },
55 "_Map Properties" => {
56 callback => sub { $self->open_map_prop },
57 accelerator => "<ctrl>P"
58 },
59 "_Map Resize" => {
60 callback => sub { $self->open_resize_map },
61 accelerator => "<ctrl>R"
62 },
63 ]
64 },
65 _Go => {
66 item_type => '<Branch>',
67 children => [
68 "_Up" => {
69 callback => sub { $self->follow ('u') },
70 accelerator => "<ctrl>U"
71 },
72 "_Down" => {
73 callback => sub { $self->follow ('d') },
74 accelerator => "<ctrl>D"
75 },
76 "_Right" => {
77 callback => sub { $self->follow ('r') },
78 accelerator => "<ctrl>R"
79 },
80 "_Left" => {
81 callback => sub { $self->follow ('l') },
82 accelerator => "<ctrl>L"
83 },
84 ]
85 },
86 _Help => {
87 item_type => '<Branch>',
88 children => [
89 _Help => {
90 callback => sub { $::MAINWIN->show_help_window },
91 accelerator => "<ctrl>H"
92 },
93 ]
94 },
95 ];
96
97 my $men =
98 Gtk2::SimpleMenu->new (
99 menu_tree => $menu_tree,
100 default_callback => \&default_cb,
101 );
102
103 for (
104 [i => 'pick'],
105 [p => 'place'],
106 [e => 'erase'],
107 [s => 'select'],
108 [l => 'eval'],
109 [x => 'connectexit'],
110 [f => 'followexit']
111 )
112 {
113 my $tool = $_->[1];
114 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
115 sub { $::MAINWIN->set_edit_tool ($tool) });
116 }
117
118 $self->add_accel_group ($men->{accel_group});
119
120 return $men->{widget};
121}
122
123sub ea {
124 my ($self) = @_;
125 $self->{ea_alt} || $::MAINWIN->{sel_editaction};
126}
127
25sub INIT_INSTANCE { 128sub INIT_INSTANCE {
26 my ($self) = @_; 129 my ($self) = @_;
27 130
28 my $map = new Crossfire::MapWidget; 131 $self->set_title ('gce - map editor');
132 $self->add (my $vb = Gtk2::VBox->new);
29 133
30 # XXX: Make a nicer pick-view (with picture) 134 $self->signal_connect (delete_event => sub { $self->delete; 1 });
31 $self->pack_start (my $lbl = Gtk2::Label->new ("no selection"), 0, 1, 0);
32 $self->{pick_view} = $lbl;
33 135
34 $self->pack_start ($map, 1, 1, 0); 136 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
35 $self->{map} = $map; 137
138 $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
139
140 $self->signal_connect (focus_in_event => sub {
141 my $ea = $::MAINWIN->{sel_editaction};
142 if ($ea->special_arrow) {
143 $self->{map}->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($ea->special_arrow));
144 } else {
145 $self->{map}->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
146 }
147 delete $self->{ea_alt};
148 });
36 149
37 $map->signal_connect (key_press_event => sub { 150 $map->signal_connect (key_press_event => sub {
38 my ($map, $event) = @_; 151 my ($map, $event) = @_;
39 152
40 $event->keyval == $Gtk2::Gdk::Keysyms{Shift_L} 153 my $kv = $event->keyval;
41 and $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_QUESTION_ARROW'));
42 154
43 $event->keyval == $Gtk2::Gdk::Keysyms{Control_L} 155 my ($x, $y) = $map->coord ($map->get_pointer);
156 for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
157 [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
158 [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
159 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
160 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
161 )
162 {
163 if ($kv == $Gtk2::Gdk::Keysyms{$_->[0]}) {
164 $_->[1]->();
165 }
166 }
167
168 if ($self->ea->special_arrow) {
44 and $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_HAND2')); 169 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
45 170 }
171 1;
46 }); 172 });
47
48 # XXX: What happens if the release got lost?
49 $map->signal_connect (key_release_event => sub { 173 $map->signal_connect (key_release_event => sub {
50 my ($map, $event) = @_; 174 my ($map, $event) = @_;
51 175
176 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
177 or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
178 {
179 delete $self->{ea_alt};
180 }
181
182 if ($self->ea->special_arrow) {
183 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
184 } else {
185 # XXX: Get the original cursor and insert it here
52 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR')); 186 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
53 187 }
188 1;
54 }); 189 });
55 $map->signal_connect (button_press_event => sub { 190 $map->signal_connect (button_press_event => sub {
56 my ($map, $event) = @_; 191 my ($map, $event) = @_;
57 192
58 my ($x, $y) = $map->coord ($event->x, $event->y); 193 my ($x, $y) = $map->coord ($event->x, $event->y);
59 my $as = $self->{map}->get ($x, $y); 194 my $as = $map->get ($x, $y);
60 195
61 if ($event->button == 1) { 196 if ((not $self->{draw_mode}) and $event->button != 2) {
62 197
63 if ($event->state * "control-mask") { 198 my $ea = $self->ea;
64 $self->update_pick ($as->[-1]) if @$as;
65 }
66 199
67 unless ($event->state * "shift-mask") { 200 $ea->begin ($map, $x, $y, $self)
68 $map->disable_tooltip unless $self->{draw_mode}; 201 if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
69 $self->{draw_mode} = [1, $x, $y];
70 $self->place_pick ($x, $y);
71 $as = $self->{map}->get ($x, $y);
72 }
73 202
74 $GCE::MainWindow::MAINWIN->update_attr_editor ($as->[-1]) if @$as;
75 $GCE::MainWindow::MAINWIN->update_stack_view ($self, $as, $x, $y);
76
77 return 1;
78
79 } elsif ($event->button == 3) {
80
81 $map->disable_tooltip unless $self->{draw_mode};
82 $self->{draw_mode} = [2, $x, $y]; 203 $self->{draw_mode} = [$x, $y];
83 $self->delete_arch ($x, $y);
84 204
85 $as = $self->{map}->get ($x, $y); 205 $ea->want_cursor
86 $GCE::MainWindow::MAINWIN->update_attr_editor ($as->[-1]) if @$as; 206 or $map->disable_tooltip;
87 $GCE::MainWindow::MAINWIN->update_stack_view ($self, $as, $x, $y);
88 207
89 return 1; 208 return 1;
90 } 209 }
91
92 0 210 0
93 }); 211 });
94 212
95 $map->signal_connect_after (motion_notify_event => sub { 213 $map->signal_connect_after (motion_notify_event => sub {
96 my ($map, $event) = @_; 214 my ($map, $event) = @_;
97 215
98 $self->{draw_mode} 216 $self->{draw_mode}
99 or return; 217 or return;
100 218
219 my $ea = $self->ea;
220
101 my ($X, $Y) = @{$self->{draw_mode}}[1,2]; 221 my ($X, $Y) = @{$self->{draw_mode}}[0,1];
102 my ($x, $y) = $map->coord ($map->get_pointer); 222 my ($x, $y) = $map->coord ($map->get_pointer);
103 223
104 while ($x != $X || $y != $Y) { 224 while ($x != $X || $y != $Y) {
105 225
106 $X++ if $X < $x; 226 $X++ if $X < $x;
107 $X-- if $X > $x; 227 $X-- if $X > $x;
108 $Y++ if $Y < $y; 228 $Y++ if $Y < $y;
109 $Y-- if $Y > $y; 229 $Y-- if $Y > $y;
110 230
111 if ($self->{draw_mode}[0] == 1) { 231 $ea->edit ($map, $X, $Y, $self)
112 232 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
113 $self->place_pick ($X, $Y);
114
115 } elsif ($self->{draw_mode}[0] == 2) {
116
117 $self->delete_arch ($X, $Y);
118 } 233 }
119 }
120 234
121 @{$self->{draw_mode}}[1,2] = ($X, $Y); 235 @{$self->{draw_mode}}[0,1] = ($X, $Y);
122 236
123 1 237 1
124 }); 238 });
125 239
126 $map->signal_connect (button_release_event => sub { 240 $map->signal_connect (button_release_event => sub {
127 my ($map, $event) = @_; 241 my ($map, $event) = @_;
128 242
129 $map->enable_tooltip if delete $self->{draw_mode}; 243 if ($self->{draw_mode}) {
244 my ($x, $y) = $map->coord ($map->get_pointer);
245
246 my $ea = $self->ea;
247 $ea->end ($map, $x, $y, $self);
248
249 delete $self->{draw_mode};
250
251 $ea->want_cursor
252 or $map->enable_tooltip;
253
254 return 1;
255 }
130 256
131 0 257 0
132 }); 258 });
133} 259}
134 260
135sub set_place_arch { 261# FIXME: Fix the automatic update of the attribute editor! and also the stack view!
262sub undo {
136 my ($self, $arch) = @_; 263 my ($self) = @_;
137 264
138 $self->{pick_arch} = $arch; 265 my $map = $self->{map}; # the Crossfire::MapWidget
139}
140 266
141sub delete_arch { 267 $map->{undo_stack_pos}
142 my ($self, $x, $y) = @_;
143
144 defined $self->{map}
145 or return 0; 268 or return;
146 269
147 my $as = $self->{map}->get ($x, $y); 270 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
148 pop @$as;
149 $self->{map}->set ($x, $y, $as);
150} 271}
151 272
152sub place_pick { 273sub redo {
153 my ($self, $x, $y) = @_; 274 my ($self) = @_;
154 275
155 defined $self->{map} 276 my $map = $self->{map}; # the Crossfire::MapWidget
156 and defined $self->{pick_arch} 277
278 $map->{undo_stack}
279 and $map->{undo_stack_pos} < @{$map->{undo_stack}}
157 or return 0; 280 or return;
158 281
159 my $as = $self->{map}->get ($x, $y); 282 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
283}
160 284
161 my $arch = { 285sub delete {
162 _name => $self->{pick_arch}->{_name}, 286 my ($self) = @_;
287
288 # check and modla dialog if "dirty"
289
290 if ($self->{mapkey}) {
291 # XXX: This should be in a delete event handler in the MainWindow.pm... but it doesnt work
292 delete $::MAINWIN->{loaded_maps}->{$self->{mapkey}};
163 }; 293 }
164 294
165 push @$as, $arch 295 $self->destroy;
166 unless @$as && $as->[-1]->{_name} eq $arch->{_name};
167
168 $self->{map}->set ($x, $y, $as);
169} 296}
170 297
171sub open_map { 298sub open_map {
172 my ($self, $path) = @_; 299 my ($self, $path, $key) = @_;
173 300
301 $self->{mapkey} = $key;
302
303 if (ref $path) {
304 $self->{map}->set_map ($path);
305
306 } else {
307 $self->{path} = $path;
308# print "OPENMAP $path\n";
174 $self->{map}->set_map (new_from_file Crossfire::Map $path); 309 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
310 require Data::Dumper;
311# print "FOO:" .Data::Dumper::Dumper ($m) . "\n";
312 }
175} 313}
176 314
177sub update_pick { 315sub save_map {
316 my ($self) = @_;
317
318 if ($self->{path}) {
319 $self->{map}{map}->write_file ($self->{path});
320 quick_msg ($self, "saved to $self->{path}");
321 } else {
322 $self->save_map_as;
323 }
324}
325
326sub save_map_as {
327 my ($self) = @_;
328
329 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
330
331 if ('ok' eq $fc->run) {
332
333 $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
334 $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
335
336 $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
337 quick_msg ($self, "saved to $self->{path}");
338 }
339
340 $fc->destroy;
341}
342
343sub _add_prop_entry {
344 my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_;
345
346 my $edwid;
347
348 if ($type eq 'string') {
349 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
350 $edwid = Gtk2::Entry->new;
351 $edwid->set_text ($self->{map}{map}{info}{$key});
352 $edwid->signal_connect (changed => sub {
353 $self->{map}{map}{info}{$key} = $_[0]->get_text;
354 if ($changecb) {
355 $changecb->($_[0]->get_text);
356 }
357 });
358 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
359
360 } elsif ($type eq 'button') {
361 $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1);
362 $b->signal_connect (clicked => ($changecb || sub {}));
363
364 } elsif ($type eq 'label') {
365 $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
366 $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key});
367 $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
368
369 } elsif ($type eq 'check') {
370 $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
371 $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1);
372 $lbl->set_active ($self->{map}{map}{info}{$key});
373 $lbl->signal_connect (toggled => sub {
374 my ($lbl) = @_;
375 $self->{map}{map}{info}{$key} = $lbl->get_active * 1;
376 ($changecb || sub {})->($lbl->get_active);
377 });
378
379 } elsif ($type eq 'sep') {
380 $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1);
381 } else {
382 $edwid = Gtk2::Label->new ("FOO");
383 }
384}
385
386sub open_resize_map {
387 my ($self) = @_;
388
389 my $w = Gtk2::Window->new ('toplevel');
390 $w->set_default_size (250, 150);
391 $w->add (my $sw = Gtk2::ScrolledWindow->new);
392 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
393 $sw->set_policy ('automatic', 'automatic');
394 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
395
396 my $i = 0;
397 for (
398 [qw/width Width string/],
399 [qw/height Height string/],
400 [qw/save Save button/,
401 sub {
402 $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height});
403 $self->{map}->invalidate_all;
404 $w->destroy;
405 }
406 ],
407 )
408 {
409 $self->_add_prop_entry ($t, $i++, @$_);
410 }
411
412 $w->show_all;
413}
414
415sub follow {
178 my ($self, $arch) = @_; 416 my ($self, $dir) = @_;
179 417
180 $self->{pick_arch} = $arch; 418 my %dir_to_path = (
181 $self->{pick_view}->set_text ($arch->{_name}); 419 u => 'tile_path_1',
420 d => 'tile_path_3',
421 r => 'tile_path_2',
422 l => 'tile_path_4',
423 );
424
425 defined $dir_to_path{$dir}
426 or return;
427 my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
428 or return;
429
430 $map = map2abs ($map, $self);
431 $::MAINWIN->open_map_editor ($map);
432}
433
434sub open_map_prop {
435 my ($self) = @_;
436
437
438 my $w = Gtk2::Window->new ('toplevel');
439 $w->set_default_size (500, 500);
440 $w->add (my $sw = Gtk2::ScrolledWindow->new);
441 $sw->add_with_viewport (my $v = Gtk2::VBox->new);
442 $sw->set_policy ('automatic', 'automatic');
443 $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
444
445 my $i = 0;
446 for (
447 [qw/name Name string/],
448 [qw/region Region string/],
449 [qw/enter_x Enter-x string/],
450 [qw/enter_y Enter-y string/],
451 [qw/reset_timeout Reset-timeout string/],
452 [qw/swap_time Swap-timeout string/],
453 [qw/x x sep/],
454 [qw/difficulty Difficulty string/],
455 [qw/windspeed Windspeed string/],
456 [qw/pressure Pressure string/],
457 [qw/humid Humid string/],
458 [qw/temp Temp string/],
459 [qw/darkness Darkness string/],
460 [qw/sky Sky string/],
461 [qw/winddir Winddir string/],
462 [qw/x x sep/],
463 [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
464 [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
465 [qw/x x sep/],
466 [qw/msg Text text/],
467 [qw/maplore Maplore text/],
468 [qw/outdoor Outdoor check/],
469 [qw/unique Unique check/],
470 [qw/fixed_resettime Fixed-resettime check/],
471 [qw/x x sep/],
472 [qw/tile_path_1 Northpath string/],
473 [qw/tile_path_2 Eastpath string/],
474 [qw/tile_path_3 Southpath string/],
475 [qw/tile_path_4 Westpath string/],
476 [qw/tile_path_5 Toppath string/],
477 [qw/tile_path_6 Bottompath string/],
478 )
479 {
480 $self->_add_prop_entry ($t, $i++, @$_);
481 }
482
483 $w->show_all;
182} 484}
183 485
184=head1 AUTHOR 486=head1 AUTHOR
185 487
186 Marc Lehmann <schmorp@schmorp.de> 488 Marc Lehmann <schmorp@schmorp.de>

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines