ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.28
Committed: Sun Mar 19 19:30:38 2006 UTC (18 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.27: +1 -1 lines
Log Message:
many changes. fixed a bug in stack view with virtual tiles.

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