ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/cf.schmorp.de/gde/GCE/MapEditor.pm
Revision: 1.35
Committed: Tue Apr 4 11:29:54 2006 UTC (20 years, 7 months ago) by elmex
Branch: MAIN
Changes since 1.34: +24 -0 lines
Log Message:
Removed pick and made the attribut editor 'the current pick'.
Added context menu to map view with an follow item in it.

File Contents

# User Rev Content
1 elmex 1.1 package GCE::MapEditor;
2    
3     =head1 NAME
4    
5     GCE::MapEditor - the map editing widget
6    
7     =cut
8    
9     use Gtk2;
10     use Gtk2::Gdk::Keysyms;
11     use Gtk2::SimpleMenu;
12    
13     use Crossfire;
14 root 1.3 use Crossfire::Map;
15 elmex 1.1 use Crossfire::MapWidget;
16    
17     use GCE::AttrEdit;
18 elmex 1.22 use GCE::Util;
19 elmex 1.1
20 elmex 1.4 use Glib::Object::Subclass
21 elmex 1.6 Gtk2::Window;
22 elmex 1.1
23     use strict;
24    
25 elmex 1.35 sub do_context_menu {
26     my ($self, $map, $event) = @_;
27    
28     my ($x, $y) = $map->coord ($event->x, $event->y);
29    
30     my $menu = Gtk2::Menu->new;
31     foreach my $cm (
32     [
33     Follow => sub {
34     $::MAINWIN->{edit_collection}{followexit}->begin ($map, $x, $y, $self)
35     }
36     ]
37     ) {
38     my $item = Gtk2::MenuItem->new ($cm->[0]);
39     $menu->append ($item);
40     $item->show;
41     $item->signal_connect (activate => $cm->[1]);
42     }
43     $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
44     }
45    
46 root 1.9 sub build_menu {
47     my ($self) = @_;
48    
49     my $menu_tree = [
50     _File => {
51     item_type => '<Branch>',
52     children => [
53     "_Save" => {
54 root 1.10 callback => sub { $self->save_map },
55 root 1.9 accelerator => '<ctrl>S'
56     },
57 root 1.10 "Save As" => {
58     callback => sub { $self->save_map_as },
59     },
60 elmex 1.32 "_Map Properties" => {
61     callback => sub { $self->open_map_prop },
62     accelerator => "<ctrl>P"
63     },
64     "_Map Resize" => {
65     callback => sub { $self->open_resize_map },
66     accelerator => "<ctrl>R"
67     },
68 root 1.13 "Close" => {
69 elmex 1.31 callback => sub { $self->destroy },
70 root 1.13 },
71 root 1.10 ]
72 root 1.9 },
73     _Edit => {
74     item_type => '<Branch>',
75     children => [
76     "_Undo" => {
77     callback => sub { $self->undo },
78 elmex 1.17 accelerator => "<ctrl>Z"
79 root 1.9 },
80     "_Redo" => {
81     callback => sub { $self->redo },
82 elmex 1.17 accelerator => "<ctrl>Y"
83     },
84 root 1.9 ]
85     },
86 elmex 1.27 _Go => {
87     item_type => '<Branch>',
88     children => [
89     "_Up" => {
90     callback => sub { $self->follow ('u') },
91 elmex 1.33 accelerator => "<ctrl>Up"
92 elmex 1.27 },
93     "_Down" => {
94     callback => sub { $self->follow ('d') },
95 elmex 1.33 accelerator => "<ctrl>Down"
96 elmex 1.27 },
97     "_Right" => {
98     callback => sub { $self->follow ('r') },
99 elmex 1.33 accelerator => "<ctrl>Right"
100 elmex 1.27 },
101     "_Left" => {
102     callback => sub { $self->follow ('l') },
103 elmex 1.33 accelerator => "<ctrl>Left"
104 elmex 1.27 },
105     ]
106     },
107 elmex 1.29 _Help => {
108     item_type => '<Branch>',
109     children => [
110 elmex 1.30 _Manual => {
111 elmex 1.29 callback => sub { $::MAINWIN->show_help_window },
112     accelerator => "<ctrl>H"
113     },
114     ]
115     },
116 root 1.9 ];
117    
118     my $men =
119     Gtk2::SimpleMenu->new (
120     menu_tree => $menu_tree,
121     default_callback => \&default_cb,
122     );
123    
124 elmex 1.23 for (
125     [i => 'pick'],
126     [p => 'place'],
127 elmex 1.24 [e => 'erase'],
128     [s => 'select'],
129     [l => 'eval'],
130     [x => 'connectexit'],
131     [f => 'followexit']
132 elmex 1.23 )
133     {
134 elmex 1.24 my $tool = $_->[1];
135 elmex 1.23 $men->{accel_group}->connect ($Gtk2::Gdk::Keysyms{$_->[0]}, [], 'visible',
136 elmex 1.24 sub { $::MAINWIN->set_edit_tool ($tool) });
137 elmex 1.23 }
138    
139 root 1.9 $self->add_accel_group ($men->{accel_group});
140    
141     return $men->{widget};
142     }
143    
144 elmex 1.31 sub set_edit_tool {
145     my ($self, $tool) = @_;
146    
147     $self->{etool} = $tool;
148    
149     if ($self->ea->special_arrow) {
150     $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
151 elmex 1.34 } else {
152     # FIXME: Get the original cursor and insert it here
153     $self->{map}{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
154 elmex 1.31 }
155     }
156    
157 elmex 1.19 sub ea {
158     my ($self) = @_;
159 elmex 1.31 $self->{ea_alt} || $self->{etool};
160 elmex 1.19 }
161    
162 elmex 1.1 sub INIT_INSTANCE {
163     my ($self) = @_;
164    
165 elmex 1.6 $self->set_title ('gce - map editor');
166     $self->add (my $vb = Gtk2::VBox->new);
167 elmex 1.1
168 root 1.9 $vb->pack_start (my $menu = $self->build_menu, 0, 1, 0);
169    
170     $vb->pack_start (my $map = $self->{map} = Crossfire::MapWidget->new, 1, 1, 0);
171    
172 elmex 1.31 $map->signal_connect_after (key_press_event => sub {
173 elmex 1.19 my ($map, $event) = @_;
174 root 1.9
175 elmex 1.19 my $kv = $event->keyval;
176 root 1.9
177 elmex 1.31 my $ret = 0;
178    
179 elmex 1.23 my ($x, $y) = $map->coord ($map->get_pointer);
180     for ([Control_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{erase} }],
181     [Alt_L => sub { $self->{ea_alt} = $::MAINWIN->{edit_collection}{pick} }],
182     [c => sub { $::MAINWIN->{edit_collection}{select}->copy }],
183 elmex 1.28 [v => sub { $::MAINWIN->{edit_collection}{select}->paste ($map, $x, $y) }],
184 elmex 1.24 [n => sub { $::MAINWIN->{edit_collection}{select}->invoke }],
185 elmex 1.23 )
186     {
187 elmex 1.34 my $ed = $_;
188    
189     if ($kv == $Gtk2::Gdk::Keysyms{$ed->[0]}) {
190     my $was_in_draw = defined $self->{draw_mode};
191    
192     $self->stop_drawmode ($map)
193     if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
194    
195     $ed->[1]->();
196 elmex 1.31 $ret = 1;
197 elmex 1.34
198     $self->start_drawmode ($map)
199     if $was_in_draw && grep { $ed->[0] eq $_ } qw/Control_L Alt_L/;
200 elmex 1.23 }
201 elmex 1.19 }
202 root 1.9
203 elmex 1.19 if ($self->ea->special_arrow) {
204     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
205 elmex 1.34 } else {
206     # FIXME: Get the original cursor and insert it here
207     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
208 elmex 1.19 }
209 elmex 1.31
210     $ret
211 elmex 1.19 });
212 elmex 1.31
213     $map->signal_connect_after (key_release_event => sub {
214 elmex 1.19 my ($map, $event) = @_;
215    
216 elmex 1.31 my $ret = 0;
217    
218 elmex 1.19 if ($event->keyval == $Gtk2::Gdk::Keysyms{Control_L}
219     or $event->keyval == $Gtk2::Gdk::Keysyms{Alt_L})
220     {
221 elmex 1.34 my $was_in_draw = defined $self->{draw_mode};
222    
223     $self->stop_drawmode ($map)
224     if $was_in_draw;
225    
226 elmex 1.19 delete $self->{ea_alt};
227 elmex 1.31 $ret = 1;
228 elmex 1.34
229     $self->start_drawmode ($map)
230     if $was_in_draw;
231 elmex 1.19 }
232    
233     if ($self->ea->special_arrow) {
234     $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ($self->ea->special_arrow));
235     } else {
236 elmex 1.31 # FIXME: Get the original cursor and insert it here
237 elmex 1.19 $map->{window}->set_cursor (Gtk2::Gdk::Cursor->new ('GDK_LEFT_PTR'));
238     }
239 elmex 1.31
240     $ret
241 elmex 1.19 });
242 elmex 1.31 $map->signal_connect_after (button_press_event => sub {
243 elmex 1.19 my ($map, $event) = @_;
244 elmex 1.1
245 elmex 1.31 if ((not $self->{draw_mode}) and $event->button == 1) {
246 elmex 1.19 my $ea = $self->ea;
247 elmex 1.4
248 elmex 1.34 $self->start_drawmode ($map);
249 root 1.9
250 elmex 1.19 $ea->want_cursor
251     or $map->disable_tooltip;
252 root 1.9
253 elmex 1.19 return 1;
254 elmex 1.35 } elsif ($event->button == 3) {
255     $self->do_context_menu ($map, $event);
256     return 1;
257 elmex 1.19 }
258 elmex 1.31
259 elmex 1.19 0
260 root 1.9 });
261    
262     $map->signal_connect_after (motion_notify_event => sub {
263     my ($map, $event) = @_;
264    
265     $self->{draw_mode}
266     or return;
267    
268 elmex 1.19 my $ea = $self->ea;
269    
270     my ($X, $Y) = @{$self->{draw_mode}}[0,1];
271 root 1.9 my ($x, $y) = $map->coord ($map->get_pointer);
272 elmex 1.4
273 root 1.9 while ($x != $X || $y != $Y) {
274 elmex 1.4
275 root 1.9 $X++ if $X < $x;
276     $X-- if $X > $x;
277     $Y++ if $Y < $y;
278     $Y-- if $Y > $y;
279 elmex 1.4
280 elmex 1.22 $ea->edit ($map, $X, $Y, $self)
281 elmex 1.15 if $X >= 0 and $Y >= 0 and $X < $map->{map}{width} and $Y < $map->{map}{height};
282 root 1.9 }
283 elmex 1.4
284 elmex 1.19 @{$self->{draw_mode}}[0,1] = ($X, $Y);
285 elmex 1.1
286 root 1.9 1
287     });
288 elmex 1.1
289 elmex 1.31 $map->signal_connect_after (button_release_event => sub {
290 root 1.9 my ($map, $event) = @_;
291 elmex 1.1
292 elmex 1.31 if ($self->{draw_mode} and $event->button == 1) {
293 elmex 1.19 my $ea = $self->ea;
294 elmex 1.1
295 elmex 1.34 $self->stop_drawmode ($map);
296 elmex 1.4
297 root 1.9 $ea->want_cursor
298     or $map->enable_tooltip;
299 elmex 1.1
300 root 1.9 return 1;
301     }
302 elmex 1.1
303 root 1.9 0
304     });
305     }
306 elmex 1.8
307 elmex 1.34 sub start_drawmode {
308     my ($self, $map) = @_;
309    
310     $self->{draw_mode} and return;
311    
312     # XXX: is this okay? my ($x, $y) = $map->coord ($event->x, $event->y);
313     my ($x, $y) = $map->coord ($map->get_pointer);
314    
315     my $ea = $self->ea;
316    
317     $ea->begin ($map, $x, $y, $self)
318     if $x >= 0 and $y >= 0 and $x < $map->{map}{width} and $y < $map->{map}{height};
319    
320     $self->{draw_mode} = [$x, $y];
321     }
322    
323     sub stop_drawmode {
324     my ($self, $map) = @_;
325    
326     $self->{draw_mode} or return;
327    
328     my ($x, $y) = $map->coord ($map->get_pointer);
329    
330     my $ea = $self->ea;
331     $ea->end ($map, $x, $y, $self);
332    
333     delete $self->{draw_mode};
334     }
335    
336 elmex 1.16 # FIXME: Fix the automatic update of the attribute editor! and also the stack view!
337 root 1.9 sub undo {
338     my ($self) = @_;
339 elmex 1.8
340 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
341 elmex 1.1
342 root 1.9 $map->{undo_stack_pos}
343     or return;
344 elmex 1.1
345 root 1.9 $map->change_swap ($map->{undo_stack}[--$map->{undo_stack_pos}]);
346     }
347 elmex 1.1
348 root 1.9 sub redo {
349     my ($self) = @_;
350 elmex 1.7
351 root 1.9 my $map = $self->{map}; # the Crossfire::MapWidget
352 elmex 1.7
353 elmex 1.14 $map->{undo_stack}
354     and $map->{undo_stack_pos} < @{$map->{undo_stack}}
355     or return;
356 elmex 1.1
357 root 1.9 $map->change_swap ($map->{undo_stack}[$map->{undo_stack_pos}++]);
358 root 1.3 }
359    
360     sub open_map {
361 elmex 1.26 my ($self, $path, $key) = @_;
362    
363     $self->{mapkey} = $key;
364 root 1.3
365 elmex 1.18 if (ref $path) {
366     $self->{map}->set_map ($path);
367    
368     } else {
369     $self->{path} = $path;
370 elmex 1.17 # print "OPENMAP $path\n";
371 elmex 1.18 $self->{map}->set_map (my $m = new_from_file Crossfire::Map $path);
372     require Data::Dumper;
373     # print "FOO:" .Data::Dumper::Dumper ($m) . "\n";
374     }
375 root 1.3 }
376    
377 root 1.13 sub save_map {
378     my ($self) = @_;
379    
380 elmex 1.19 if ($self->{path}) {
381     $self->{map}{map}->write_file ($self->{path});
382 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
383     } else {
384     $self->save_map_as;
385 elmex 1.19 }
386 root 1.13 }
387    
388     sub save_map_as {
389     my ($self) = @_;
390 elmex 1.19
391 elmex 1.22 my $fc = $::MAINWIN->new_filechooser ('gce - save map', 1, $self->{path});
392 elmex 1.19
393     if ('ok' eq $fc->run) {
394    
395     $::MAINWIN->{fc_last_folder} = $fc->get_current_folder;
396     $::MAINWIN->{fc_last_folders}->{$self->{fc_last_folder}}++;
397    
398     $self->{map}{map}->write_file ($self->{path} = $fc->get_filename);
399 elmex 1.22 quick_msg ($self, "saved to $self->{path}");
400 elmex 1.19 }
401    
402     $fc->destroy;
403 root 1.13 }
404    
405 elmex 1.17 sub _add_prop_entry {
406     my ($self, $table, $idx, $key, $desc, $type, $changecb) = @_;
407    
408     my $edwid;
409    
410     if ($type eq 'string') {
411     $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
412     $edwid = Gtk2::Entry->new;
413     $edwid->set_text ($self->{map}{map}{info}{$key});
414     $edwid->signal_connect (changed => sub {
415     $self->{map}{map}{info}{$key} = $_[0]->get_text;
416     if ($changecb) {
417     $changecb->($_[0]->get_text);
418     }
419     });
420     $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
421    
422     } elsif ($type eq 'button') {
423     $table->attach_defaults (my $b = Gtk2::Button->new_with_label ($desc), 0, 2, $idx, $idx + 1);
424     $b->signal_connect (clicked => ($changecb || sub {}));
425    
426     } elsif ($type eq 'label') {
427     $table->attach_defaults (my $lbl = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
428     $edwid = Gtk2::Label->new ($self->{map}{map}{info}{$key});
429     $table->attach_defaults ($edwid, 1, 2, $idx, $idx + 1);
430    
431 elmex 1.27 } elsif ($type eq 'check') {
432     $table->attach_defaults (my $lbl1 = Gtk2::Label->new ($desc), 0, 1, $idx, $idx + 1);
433     $table->attach_defaults (my $lbl = Gtk2::CheckButton->new, 1, 2, $idx, $idx + 1);
434     $lbl->set_active ($self->{map}{map}{info}{$key});
435     $lbl->signal_connect (toggled => sub {
436     my ($lbl) = @_;
437     $self->{map}{map}{info}{$key} = $lbl->get_active * 1;
438     ($changecb || sub {})->($lbl->get_active);
439     });
440    
441     } elsif ($type eq 'sep') {
442     $table->attach_defaults (my $lbl1 = Gtk2::HSeparator->new, 0, 2, $idx, $idx + 1);
443 elmex 1.17 } else {
444     $edwid = Gtk2::Label->new ("FOO");
445     }
446     }
447    
448     sub open_resize_map {
449     my ($self) = @_;
450    
451     my $w = Gtk2::Window->new ('toplevel');
452     $w->set_default_size (250, 150);
453     $w->add (my $sw = Gtk2::ScrolledWindow->new);
454     $sw->add_with_viewport (my $v = Gtk2::VBox->new);
455     $sw->set_policy ('automatic', 'automatic');
456     $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
457    
458     my $i = 0;
459     for (
460     [qw/width Width string/],
461     [qw/height Height string/],
462     [qw/save Save button/,
463     sub {
464     $self->{map}{map}->resize ($self->{map}{map}{info}{width}, $self->{map}{map}{info}{height});
465 elmex 1.19 $self->{map}->invalidate_all;
466     $w->destroy;
467 elmex 1.17 }
468     ],
469     )
470     {
471     $self->_add_prop_entry ($t, $i++, @$_);
472     }
473    
474     $w->show_all;
475     }
476    
477 elmex 1.27 sub follow {
478     my ($self, $dir) = @_;
479    
480     my %dir_to_path = (
481     u => 'tile_path_1',
482     d => 'tile_path_3',
483     r => 'tile_path_2',
484     l => 'tile_path_4',
485     );
486    
487     defined $dir_to_path{$dir}
488     or return;
489     my $map = $self->{map}{map}{info}{$dir_to_path{$dir}}
490     or return;
491    
492     $map = map2abs ($map, $self);
493     $::MAINWIN->open_map_editor ($map);
494     }
495    
496 elmex 1.17 sub open_map_prop {
497     my ($self) = @_;
498    
499    
500     my $w = Gtk2::Window->new ('toplevel');
501     $w->set_default_size (500, 500);
502     $w->add (my $sw = Gtk2::ScrolledWindow->new);
503     $sw->add_with_viewport (my $v = Gtk2::VBox->new);
504     $sw->set_policy ('automatic', 'automatic');
505     $v->pack_start (my $t = Gtk2::Table->new (2, 10), 0, 0, 0);
506    
507     my $i = 0;
508     for (
509 elmex 1.27 [qw/name Name string/],
510     [qw/region Region string/],
511     [qw/enter_x Enter-x string/],
512     [qw/enter_y Enter-y string/],
513     [qw/reset_timeout Reset-timeout string/],
514     [qw/swap_time Swap-timeout string/],
515     [qw/x x sep/],
516     [qw/difficulty Difficulty string/],
517     [qw/windspeed Windspeed string/],
518     [qw/pressure Pressure string/],
519     [qw/humid Humid string/],
520     [qw/temp Temp string/],
521     [qw/darkness Darkness string/],
522     [qw/sky Sky string/],
523     [qw/winddir Winddir string/],
524     [qw/x x sep/],
525     [qw/width Width label/], # sub { $self->{map}{map}->resize ($_[0], $self->{map}{map}{height}) }],
526     [qw/height Height label/], # sub { $self->{map}{map}->resize ($self->{map}{map}{width}, $_[0]) }],
527     [qw/x x sep/],
528     [qw/msg Text text/],
529     [qw/maplore Maplore text/],
530     [qw/outdoor Outdoor check/],
531     [qw/unique Unique check/],
532     [qw/fixed_resettime Fixed-resettime check/],
533     [qw/x x sep/],
534     [qw/tile_path_1 Northpath string/],
535     [qw/tile_path_2 Eastpath string/],
536     [qw/tile_path_3 Southpath string/],
537     [qw/tile_path_4 Westpath string/],
538     [qw/tile_path_5 Toppath string/],
539     [qw/tile_path_6 Bottompath string/],
540 elmex 1.17 )
541     {
542     $self->_add_prop_entry ($t, $i++, @$_);
543     }
544    
545     $w->show_all;
546     }
547    
548 elmex 1.1 =head1 AUTHOR
549    
550     Marc Lehmann <schmorp@schmorp.de>
551     http://home.schmorp.de/
552    
553     Robin Redeker <elmex@ta-sa.org>
554     http://www.ta-sa.org/
555    
556     =cut
557     1;
558