ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/gde/GCE/MapEditor.pm
Revision: 1.33
Committed: Sun Apr 2 10:58:52 2006 UTC (18 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.32: +4 -4 lines
Log Message:
changed map navigation to cursor keys and made active tab stay the same on object switch in attreditor

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