# -*- perl -*-

#
# $Id: BBBikeHeavy.pm,v 1.25 2005/12/07 22:24:54 eserte Exp $
# Author: Slaven Rezic
#
# Copyright (C) 2003 Slaven Rezic. All rights reserved.
# This package is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Mail: slaven@rezic.de
# WWW:  http://www.rezic.de/eserte/
#

package BBBikeHeavy;

$VERSION = sprintf("%d.%02d", q$Revision: 1.25 $ =~ /(\d+)\.(\d+)/);

package main;
use strict;
use BBBikeGlobalVars;
use BBBikeUtil qw(STAT_MODTIME);

BEGIN {
    if (!defined &M) {
	eval 'sub M ($) { @_ }'; warn $@ if $@;
    }
}

# Automatisches Scrolling, wenn der Benutzer die Maus zum Rand des Canvases
# bewegt.
### AutoLoad Sub
sub BBBikeHeavy::start_followmouse {
    # Bei Versionen bis einschließlich 800.013 kann man mit dieser
    # Funktion Tk zum Absturz bringen.
    # Hmmm... anscheinend auch mit 800.022 schlecht. containing() scheint
    # böse zu sein. Aber 803.023 scheint zu gehen...
    return $Tk::VERSION < 803;
    stop_followmouse(); # vorsichtshalber...
    my $scroll_lock;
    my $followmouse_last;
    my $set_scroll_lock = sub {
	$scroll_lock = $c->after(100,
				 sub { undef $scroll_lock });
    };
    $followmouse_repeat = $c->repeat
      (10, sub {
	   return if !$c;
	   my $e = $c->XEvent;
	   return if $scroll_lock or !$e;
	   # XXX Das hier scheint böse zu sein:
	   my $under_w = $c->containing($e->X, $e->Y);
	   if (!defined $under_w or $under_w ne $c) {
	       undef $followmouse_last;
	       return;
	   }
	   my($x, $y) = ($e->x, $e->y);
	   my $real_canvas_width  = $c->width;
	   my $real_canvas_height = $c->height;
	   my $pad = 30;
	   if ($x < $pad) {
	       $c->xview(scroll => -1, 'units');
	       $set_scroll_lock->();
	   }
	   if ($y < $pad) {
	       # Das Scrollen nach oben um 0.3 Sekunden verzögern, weil
	       # der Benutzer evtl. nur das Menü erreichen wollte.
	       my $now = Tk::timeofday();
	       if (!defined $followmouse_last) {
		   $followmouse_last = $now;
		   return;
	       }
	       if (defined $followmouse_last and
		   $followmouse_last+.300 < $now ) {
	       } else {
		   return;
	       }
	       $c->yview(scroll => -1, 'units');
	       $set_scroll_lock->();
	   } else {
	       undef $followmouse_last;
	   }
	   if ($x > $real_canvas_width-$pad) {
	       $c->xview(scroll => +1, 'units');
	       $set_scroll_lock->();
	   }
	   if ($y > $real_canvas_height-$pad) {
	       $c->yview(scroll => +1, 'units');
	       $set_scroll_lock->();
	   }
       });
}

### AutoLoad Sub
sub BBBikeHeavy::stop_followmouse {
    if ($followmouse_repeat) {
	$followmouse_repeat->cancel;
	undef $followmouse_repeat;
    }
}

# Dump an human-readable error message with error string $errstring for
# string eval'ed code $evalcode
sub BBBikeHeavy::string_eval_die {
    my($errstring, $evalcode) = @_;
    my($line) = $errstring =~ /line\s+(\d+)/;
    if (defined $line) {
	$line--; # human vs. machine numbering
	my(@l) = split /\n/, $evalcode;
	my $from_line = $line - 3; $from_line = 0   if $from_line < 0;
	my $to_line   = $line + 3; $to_line   = $#l if $to_line   > $#l;
	status_message("$errstring\n" .
		       join("", map { sprintf "%4d %s\n", $_+1, $l[$_] } ($from_line .. $to_line)) .
		       "\n", "die");
    } else {
	status_message($errstring, "die");
    }
}

### AutoLoad Sub
sub BBBikeHeavy::load_plugin {
    my $file = shift;
    my @plugin_args;
    if ($file =~ /^(.*)=(.*)$/) {
	$file = $1;
	@plugin_args = split / /, $2;
    }
    $file .= ".pm" if ($file !~ /\.pm$/);
    my($mod) = fileparse($file, '\..*');
    my $loading_error = 0;
    if (-r $file) {
	do $file or do {
	    # XXX use status_message etc.
	    warn "Die Datei $file konnte nicht geladen werden: $@";
	    return;
	};
	$INC{"$mod.pm"} = $file;
    } elsif (-r "$FindBin::RealBin/$file") {
	do "$FindBin::RealBin/$file" or do {
	    # XXX use status_message etc.
	    warn "Die Datei $FindBin::RealBin/$file konnte nicht geladen werden: $@";
	    return;
	};
	$INC{"$mod.pm"} = "$FindBin::RealBin/$file";
    } else {
	my $ok = 0;
	if (!file_name_is_absolute($file)) {
	    foreach my $d (@INC) {
		if (-r "$d/$file") {
		    do $file;
		    $INC{"$mod.pm"} = "$d/$file";
		    $ok = 1;
		    last;
		}
	    }
	}

	if (!$ok) {
	    eval 'require $file';
	    if ($@) {
		my $err = $@;
		status_message(Mfmt("Die Datei %s konnte nicht geladen werden. Grund: %s", $file, $err), "error");
		warn $err;
		return;
	    }
	}
    }
    eval $mod.'::register(@plugin_args)';
    if ($@) {
	my $err = $@;
	status_message(Mfmt("Das Plugin %s konnte nicht registriert werden. Grund: %s", $mod, $err), "err");
	warn $err;
	return;
    }
}

sub BBBikeHeavy::layer_editor {
    require Tk::LayerEditorToplevel;
    Tk::LayerEditorToplevel->VERSION(0.11);
    # XXX max. 1 layereditor öffnen bzw. Änderungen per Hooks an andere
    # editoren propagieren
    my @elem;
    my $reorder_elems = sub {
	@elem = (
		 {'Image' => $ampel_klein_photo,
		  'Text'  => M"Ampeln",
		  'Visible' => $p_draw{'lsa'},
		  'Data' => {Tag => 'lsa-fg',
			     Type  => 'p',
			     Subtype => 'lsa',
			    }
		 },
		 {'Image' => $search_photo,
		  'Text'  => M"Route",
		  'Visible' => (@realcoords > 0),
		  'Data' => {Group => 'route',}
		 },
		 {'Image' => $strasse_photo,
		  'Text'  => M"Straßen",
		  'Visible' => $str_draw{'s'},
		  'Data' => {Group => 'str_s',
			     Type  => 's',
			     Subtype => 's',
			    }
		 },
		 {'Image' => $landstrasse_photo,
		  'Text'  => M"Landstraßen",
		  'Visible' => $str_draw{'l'},
		  'Data' => {Group => 'str_l',
			     Type => 's',
			     Subtype => 'l',
			    }
		 },
		 {'Image' => $ort_photo,
		  'Text'  => M"Orte",
		  'Visible' => $p_draw{'o'},
		  'Data' => {Group => 'p_o',
			     Type  => 'p',
			     Subtype => 'o',
			    }
		 },
		 {'Image' => $ubahn_photo,
		  'Text'  => M"U-Bahn",
		  'Visible' => $str_draw{'u'},
		  'Data' => {Group => 'str_u',
			     Type  => 's',
			     Subtype => 'u',
			    }
		 },
		 {'Image' => $sbahn_photo,
		  'Text'  => M"S-Bahn",
		  'Visible' => $str_draw{'b'},
		  'Data' => {Group => 'str_b',
			     Type => 's',
			     Subtype => 'b',
			    }
		 },
		 {'Image' => $rbahn_photo,
		  'Text'  => M"Regionalbahn",
		  'Visible' => $str_draw{'r'},
		  'Data' => {Group => 'str_r',
			     Type => 's',
			     Subtype => 'r',
			    }
		 },
		 {'Image' => $wasser_photo,
		  'Text'  => M"Gewässser",
		  'Visible' => $str_draw{'w'},
		  'Data' => {Group => 'str_w',
			     Type => 's',
			     Subtype => 'w',
			    }
		 },
		 {'Image' => $flaechen_photo,
		  'Text'  => M"Flächen",
		  'Visible' => $str_draw{'f'},
		  'Data' => {Group => 'str_f',
			     Type => 's',
			     Subtype => 'f',
			    }
		 },
		 {'Text'  => M"Grenzen von Berlin",
		  'Visible' => $str_draw{'g'},
		  'Data' => {Group => 'str_g',
			     Type => 's',
			     Subtype => 'g',
			    }
		 },
		 {'Text'  => M"Grenzen von Potsdam",
		  'Visible' => $str_draw{'gP'},
		  'Data' => {Group => 'str_g',
			     Type => 's',
			     Subtype => 'gP',
			    }
		 },
		 {'Text'  => M"Staatsgrenzen",
		  'Visible' => $str_draw{'gD'},
		  'Data' => {Group => 'str_g',
			     Type => 's',
			     Subtype => 'gD',
			    }
		 },
		 {'Text'  => M"Kneipen etc.",
		  'Visible' => $p_draw{'kn'},
		  'Data' => {Group => 'p_kn',
			     Type => 'p',
			     Subtype => 'kn',
			    }
		 },
		 {'Text'  => M"Sehenswürdigkeiten",
		   'Image' => $star_photo,
		  'Visible' => $str_draw{'v'},
		  'Data' => {Group => 'v',
			     Type => 's',
			     Subtype => 'v',
			    }
		 },
##XXX toggling won't work with this definition alone
# 		 {'Text'  => M"Persönliche Orte",
# 		  'Visible' => $p_draw{'personal'},
# 		  'Data' => {Tag => 'personal-fg',
# 			     Type => 'p',
# 			     Subtype => 'personal',
# 			    }
# 		 },
		);
	if ($advanced) {
	    push @elem, {Image => undef,
			 Text => 'pp',
			 Visible => $p_draw{'pp'},
			 Data => {Tag => 'pp',
				  Type => 'p',
				  Subtype => 'pp',
				 }
			};
	    push @elem, {Image => undef,
			 Text => '?',
			 Visible => $str_draw{'fz'},
			 Data => {Tag => 'fz',
				  Type => 's',
				  Subtype => 'fz',
				 }
			};
	}
	while(my($abk, $val) = each %str_draw) {
	    next if $abk !~ /^L\d/;
	    if ($val && (defined $str_file{$abk} || $str_obj{$abk})) {
		my $layer_name = "Layer $abk";
		if (defined $str_file{$abk}) {
		    $layer_name .= " (" .basename($str_file{$abk}).")";
		}
		push @elem,
		    {Image => undef,
		     Text => $layer_name,
		     Visible => $val,
		     Data => {Tag => "$abk", # XXX apparently without "-s"
			      Type => 's',
			      Subtype => $abk,
			     }
		    };
	    }
	}
	while(my($abk, $val) = each %p_draw) {
	    next if $abk !~ /^L\d/;
	    if ($val && (defined $p_file{$abk} || $p_obj{$abk})) {
		my $layer_name = "Layer $abk";
		if (defined $p_file{$abk}) {
		    $layer_name .= " (" .basename($p_file{$abk}).")";
		}
		push @elem,
		    {Image => undef,
		     Text => $layer_name,
		     Visible => $val,
		     Data => {Tags => ["$abk-fg", "$abk-img", "$abk-label"],
			      Type => 'p',
			      Subtype => $abk,
			     }
		    };
	    }
	}

	my $score = 0;
	my %score;
	foreach (reverse real_stack_order()) {
	    $score{$_} = $score;
	    $score++;
	}
	# using ST :-)
	@elem = map { $_->[1] }
	        sort { $a->[0] <=> $b->[0] }
		map {
		    if (exists $tag_group{$_->{Data}{Group}} &&
			defined $tag_group{$_->{Data}{Group}} &&
			exists $score{$tag_group{$_->{Data}{Group}}->[0]}
		       ) {
			[$score{$tag_group{$_->{Data}{Group}}->[0]}, $_]
		    } else {
			my $tag = ($_->{Data}{Tags}
				   ? $_->{Data}{Tags}[0]
				   : $_->{Data}{Tag});
#XXXvvv del: sollten keine warnings mehr auftreten
if (!defined $tag) {
warn "Tag is undefined for: ";
require Data::Dumper; print STDERR "Line " . __LINE__ . ", File: " . __FILE__ . "\n" . Data::Dumper->new([$_],[])->Indent(1)->Useqq(1)->Dump; # XXX
}
#XXX^^^
			[$score{$tag}, $_] # XXX oder 0?
		    }
		} @elem;
    };
    $reorder_elems->();

    my $bbl = $top->LayerEditorToplevel
	(-title => M"Layer-Editor",
	 -closelabel => M"Schließen",
	 -orderchange => sub {
	     my $items = $_[1];
	     foreach my $item (reverse @$items) {
		 if (defined $item->{Data}{Group} && exists $tag_group{$item->{Data}{Group}}) {
		     foreach (@{ $tag_group{$item->{Data}{Group}} }) {
			 special_raise($_, 1);
		     }
		 } elsif (defined $item->{Data}{Tags}) {
		     foreach (@{ $item->{Data}{Tags} }) {
			 special_raise($_, 1);
		     }
		 } elsif (defined $item->{Data}{Tag}) {
		     special_raise($item->{Data}{Tag}, 1);
		 }
	     }
	     restack()
	 },
	 -visibilitychange => sub {
	     my($w, $data, $vis) = @_;
	     if (defined $data->{Group} && $data->{Group} eq 'route') {
		 if ($vis) {
		     # do nothing...
		 } else {
		     reset_button_command();
		 }
	     } else {
		 eval "\$" . ($data->{Type} eq 's' ? 'str' : 'p') .
		     '_draw{"' . $data->{Subtype} . '"} = ' .
			 ($vis ? "1" : "0");
		 warn __LINE__ . ": $@" if $@;
		 if (exists $check_sub{$data->{Subtype}}) {
		     $check_sub{$data->{Subtype}}->();
		 } else {
		     if ($data->{Type} eq 'p') {
			 plot('p',$data->{Subtype});
		     } else {
			 plot('str',$data->{Subtype});
		     }
		 }
	     }
	 },
	 -transient => $top,
	);
    $bbl->add(@elem);

    # Hooks
    my $bblpath = $bbl->PathName;
    for my $layer (qw(after_new_layer after_delete_layer)) {
	Hooks::get_hooks($layer)->add
		(sub {
		     $reorder_elems->();
		     $bbl->add(@elem);
		 }, $bblpath);
    }
    $bbl->OnDestroy
	(sub {
	     for my $layer (qw(after_new_layer after_delete_layer)) {
		 Hooks::get_hooks($layer)->del($bblpath);
	     }
	 });
}

# Funktionen für Darstellung einer berlinmap-Karte
# XXX mehr in eval einklammern
### AutoLoad Sub
sub BBBikeHeavy::getmap {
    my($x, $y, $type, %args) = @_;
    if (!defined $x) {
	my($px, $py) = $c->pointerxy;
	$px -= $c->rootx;
	$py -= $c->rooty;
	($x, $y) = ($c->canvasx($px), $c->canvasy($py));
    }
    if ($dont_delete_map && !$args{'-from_check'}) {
	push @map_surround_img, $map_img;
	undef $map_img;
    } else {
	delete_map();
    }

    if (!$map_draw) {
	return;
    }

    my $rotate_cmd = '';
    if (0 && $orientation eq 'portrait') { # XXXXXX
	$rotate_cmd = ' | pnmrotate -noantialias 90 ';
    } elsif ($orientation ne 'landscape') {
	status_message(M"Die Karte kann nur im Landscape-Modus gezeichnet werden.", 'warn');
	$map_draw = 0;
	return;
    }

    if (!defined $type) {
	$type = $map_default_type;
    }

    if (!$args{-fallback} && $use_map_fallback) {
	$args{-fallback} = [ grep { $_ ne $type }
			     "b2002", "b2003", "b2004", "brbmap", "de2002"
			   ];
    }

    my $o = $Karte::map{$type};
    if (!defined $o) {
	$map_draw = 0;
	die Mfmt("Dem Kartentyp %s kann kein Objekt zugeordnet werden", $type);
    }
    my $width  = $o->width;
    my $height = $o->height;
    my $bx1    = $o->x1;
    my $by2    = $o->y2;
    my $toppm  = $o->to_ppm;

    my($mapx, $mapy, $mapxx, $mapyy);
    if ($coord_system eq 'standard') {
	my($tx,$ty);
	if ($type eq 'brbmap' || $type eq 'de2002') { # XXX Check for available resources??? Also valid for other map types???
	    my $c = join(",", anti_transpose($x, $y));
	    local @INC = (@INC, "$FindBin::RealBin/miscsrc");
	    require "convert_berlinmap.pl";
	    my $ret;
	    for my $ref_dist (qw(10000 20000 40000 80000 160000 320000)) {
		$ret = BBBike::Convert::process
		    (-datafromany => "$FindBin::RealBin/misc/gps_correction_all.dat",
		     -refpoint => "$c,$ref_dist",
		     '-reverse',
		     '-nooutput', '-reusemapdata',
		    );
		last if ($ret && $ret->{Count} >= 5);
	    }
	    if ($ret) {
		my $k_obj = Karte::create_obj("Karte::Custom", %$ret);
		my $new_c = join(",", map { int }
				 $o->standard2map
				 ($k_obj->standard2map(split/,/, $c)));
		($tx,$ty) = split /,/, $new_c;
	    }
	} else {
	    ($tx,$ty) = $o->standard2map(anti_transpose($x, $y));
	}
	($mapx, $mapy, $mapxx, $mapyy) = $o->coord($tx, $ty);
    } elsif ($coord_system eq $type) {
	($mapx, $mapy, $mapxx, $mapyy) = $o->coord(anti_transpose($x, $y));
    } else {
	($mapx, $mapy, $mapxx, $mapyy) = $o->coord($coord_system_obj->map2map($o, anti_transpose($x, $y)));
	($bx1,$by2) = ($o->x1/$coord_system_obj->x1,
		       $o->y2/$coord_system_obj->y2);
	status_message("Transformation $coord_system nach $type", 'info');
    }
#    if ($orientation eq 'portrait') {
#	my $swap = $mapxx;
#	$mapxx = $mapyy;
#	$mapyy = $swap;
#    }

#warn "x,y=$x, $y; anti_transpose=" . anti_transpose($x, $y)."mapx/y=($mapx, $mapy, $mapxx, $mapyy)";

    # XXX da berlinmap-Koordinaten gegenüber den Standard-Koordinaten (Hafas)
    # gedreht sind, stimmt die Karte mit den Vektoren an den Rändern
    # nicht mehr überein.
    my($newwidth, $newheight);
    my($deltax, $deltay);
    {
	my($xx, $yy); # Zuweisung weiter oben machen XXX
	if ($coord_system eq 'standard' ||
	    ($coord_system ne $type)) {
	    ($xx, $yy) = ($bx1, $by2);
	} else {
	    ($xx, $yy) = (1, 1);
	}
	my($x0, $y0, $x1, $y1, $xd, $yd) =
	  (transpose(0, 0),
	   transpose($width  * $xx,
		     $height * $yy),
	   transpose($mapxx * $xx,
		     $mapyy * $yy),
	  );
	($newwidth, $newheight) = map { int } (abs($x1-$x0), abs($y1-$y0));
	($deltax, $deltay)      = (abs($xd-$x0), abs($yd-$y0));
    }

    my $filename = get_file_or_url($o, $mapx, $mapy);

## XXX geht nicht :(
#     # Hack: lieber XPM statt GIF verwenden, weil schneller und 8bit
#     if ($filename =~ m|^(.*map)/(...)\.gif$|) {
# 	my($dir, $coord) = ($1, $2);
# 	my $xpmfile = $dir . "_xpm/$coord.xpm.gz";
# 	if (-f $xpmfile) {
# 	    require Tk::Pixmap;
# 	    $filename = "$tmpdir/$coord.xpm";
# 	    system("zcat $xpmfile > $filename");
# 	    $tmpfiles{$filename}++;
# 	    $toppm = "xpmtoppm";
# 	}
#     }

    my $tmpfile;
    if (! -r $filename || ! -f $filename) {
	warn "Map $filename non-existent!\n";

	if ($devel_host) {
	    if ($type eq 'brbmap' && -d "$FindBin::RealBin/misc") {
		eval q{
		       use DB_File;
		       use Fcntl;
		       my %gismap;
		       tie %gismap, 'DB_File', "$FindBin::RealBin/misc/gismap_missing",
		       O_CREAT|O_RDWR, 0644, $DB_HASH;
		       $gismap{$filename}++;
		       untie %gismap;
		      };
	    }
	    warn __LINE__ . ": $@" if $@;
	}

	if ($args{-fallback} && @{ $args{-fallback} }) {
	    my $new_type = shift @{ $args{-fallback} };
	    warn "Try fallback type $new_type...\n";
	    return getmap($x, $y, $new_type, %args);
	}

	if ($devel_host) {
	    $c->createRectangle($x-$deltax, $y-$deltay,
				$x+$newwidth-$deltax, $y+$newheight-$deltay,
				-fill => 'white',
				-outline => 'red', -tags => 'map');
	    if ($o->can("coord_from_filename")) {
		my $ci = $o->coord_from_filename($filename);
		if (defined $ci) {
		    $c->createText($x+$newwidth/2-$deltax,
				   $y+$newheight/2-$deltay,
				   -anchor => 'c',
				   -text => $ci,
				   -font => $font{tiny},
				   -tags => 'map');
		}
	    }
	}
	restack_delayed();
	if (0 && $type ne 'brbmap' && $map_default_type ne 'brbmap') { # XXXX ("0 &&")
	    getmap($x, $y, 'brbmap', -recursive => 1);
	} elsif ($type ne 'berlinmap' && $map_default_type ne 'berlinmap') {
#	    getmap($x, $y, 'berlinmap', -recursive => 1);
	} else {
	    $top->bell;
	    status_message(Mfmt("Die Datei <%s> existiert nicht.", $filename),
			   'warn');
	    $map_draw = 0;
	}
	# XXX what about map_surround?
	goto CLEANUP;
    }
    $tmpfile = "$tmpdir/bbbikemap.$$";

    my $too_big = sub {
	if ($newwidth > 1400 || $newheight > 1000) {
	    status_message("Die Karte zu groß ($newwidth x $newheight) und wird nicht angezeigt", "info");
	    return 1;
	}
	0;
    };

    my $convert_image = sub {
	my($filename, $nofail) = @_;
	my $map_img;
	if ($newwidth  != $width ||
	    $newheight != $height ||
	    $rotate_cmd ne '' ||
	    $map_color =~ /^(mono|pixmap|gray)$/
	   ) {
	    return if $too_big->();
	    my $cmd;
	    $cmd = "$toppm $filename " .
	      $rotate_cmd .
		" | pnmscale -xsize $newwidth -ysize $newheight ";
	    if ($map_color eq 'color') {
		$cmd .= " > $tmpfile";
	    } elsif ($map_color eq 'gray') {
		$cmd .= " | ppmtopgm | ppmtobmp > $tmpfile";
	    } elsif ($map_color eq 'pixmap') {
		$cmd .= " | ppmquant 256 | ppmtoxpm > $tmpfile";
	    } elsif ($map_color eq 'mono') {
		$cmd .= " | ppmtopgm | pgmtopbm -floyd | pbmtoxbm > $tmpfile";
	    } else { die }
	    warn "Doing system <$cmd>\n" if $verbose;
	    system($cmd);
	    if ($?) {
		return undef if $nofail;
		status_message(Mfmt("Die Karte <%s> kann nicht mit " .
				    "<%s> und <pnmscale> " .
				    "konvertiert werden.", $filename, $toppm),
			       'warn');
		$map_draw = 0;
		goto CLEANUP;
	    }
	    eval {
		$map_img = image_from_file($top, $tmpfile,
					   -colormode => $map_color,
					   -mimetype => "image/x-ppm");
	    };
	    warn $@ if $@;
	} else {
	    eval {
		$map_img = image_from_file($top, $filename,
					   -colormode => $map_color,
					   -mimetype => $o->mimetype);
	    };
	    warn $@ if $@;
	}
	$map_img;
    };

    status_message("");
    $progress->Init; # (-dependents => $c);

    IncBusy($top);
    eval {
	eval {
	    die "map_color=$map_color rotate_cmd=$rotate_cmd"
	      if $map_color ne 'color' or $rotate_cmd ne '';
	    die "newwidth=$newwidth width=$width " .
	      "newheight=$newheight height=$height"
		if $newwidth == $width and $newheight == $height;
	    die "too big" if $too_big->();
	    require GfxConvert;
	    GfxConvert::transform_image($filename, $tmpfile,
					-in_mime => $o->mimetype,
					-out_mime => $o->mimetype,
					-width => $newwidth,
					-height => $newheight);
	    if ($o->mimetype eq 'image/png') {
		require Tk::PNG;
	    } elsif ($o->mimetype eq 'image/jpeg') {
		require Tk::JPEG;
	    } elsif ($o->mimetype eq 'image/tiff') {
		require Tk::TIFF;
	    }
	    $map_img = $top->Photo(-file => $tmpfile);
	};
	warn __LINE__ . ": $@" if $@ and $verbose;
	if ($@ || !defined $map_img) {
	    $map_img = $convert_image->($filename);
	}
	if (!defined $map_img && $@ !~ /too big/) {
	    status_message($@, 'warn');
	    $map_draw = 0;
	    return;
	}
	if ($verbose) {
	    warn "Create image $map_img at (", $x-$deltax, "/", $y-$deltay,
		 ") with anchor nw";
	}
	$c->createImage($x-$deltax, $y-$deltay,
			-image => $map_img,
			-anchor => 'nw', -tags => ['map']);

	if ($map_surround && !$o->noenvironment) {
	    $escape = 0;
	    my $progress_count = 1;
	LOOP: foreach my $ix (-1 .. 1) {
		foreach my $iy (-1 .. 1) {
		    next if $ix == 0 and $iy == 0;
		    $progress->Update($progress_count/9); $progress_count++;
		    last LOOP if ($escape);
		    my $filename = get_file_or_url($o,
						   $o->incx($mapx, $ix),
						   $o->incy($mapy, $iy));
		    next if ! -r $filename || ! -f $filename;
		    my $img = $convert_image->($filename, 'nofail');
		    next if !defined $img;
		    $c->createImage($x-$deltax+($ix*$newwidth),
				    $y-$deltay+($iy*$newheight),
				    -image => $img,
				    -anchor => 'nw', -tags => ['map']);
		    push @map_surround_img, $img;
		}
	    }
	}

	restack_delayed();
    };
  CLEANUP:
    unlink($tmpfile) if defined $tmpfile;
    $progress->Finish;
    DecBusy($top);
}

### AutoLoad Sub
sub BBBikeHeavy::get_file_or_url {
    my($o, $mapx, $mapy) = @_;
    my $filename;
  TRY: {
      TRYCACHE: {
	    # Cachefile verwenden, falls gewünscht und möglich
	    if ($use_wwwcache && $o->can('cache')) {
		my $tmpfile = $o->cache($mapx, $mapy, 0);
		# $tmpfile löschen? XXX überprüfen!
		if (-f $tmpfile) {
		    $filename = $tmpfile;
		    last TRY; # Erfolg
		}
	    }
	}

	  my $get_by_lwp = sub {
	      if ($do_wwwmap && $o->can('url')) {
		  my $ua = get_user_agent();
		  if (!$ua) {
		      return; # kein Erfolg
		  }
		  my $res;
		  my $tmpurlfile;
		  my $content = "";
		  my $get_content = sub {
		      my($chunk, $res, $prot) = @_;
		      $top->update;
		      die "Aborted" if ($escape);
		      $content .= $chunk;
		  };
		  my $req_url = $o->url($mapx, $mapy);
		  return 0 if !defined $req_url;

		  IncBusy($top);
		  eval {
		      my $req = HTTP::Request->new('GET', $req_url);
		      $tmpurlfile = "$tmpdir/bbbike_url.$$";
		      print STDERR
			  Mfmt("Die URL <%s> wird geholt (LWP) ... ",
			       $req->url);
		      $escape = 0;
		      $res = $ua->request($req, $get_content, 1024);
		  };
		  DecBusy($top);

		  if (!defined $res || !$res->is_success || $content eq '') {
		      if (!$res) {
			  status_message(Mfmt("Keine Antwort von %s",
					      $req_url));
		      } else {
			  status_message($res->as_string);
		      }
		      return; # kein Erfolg
		  } else {
		      if (!open(TMP, ">$tmpurlfile")) {
			  status_message
			      (Mfmt("Kann auf %s nicht schreiben: %s",
				    $tmpurlfile, $!));
			  return;
		      }
		      binmode TMP;
		      print TMP $content;
		      close TMP;
		  }
		  print STDERR "OK\n";
		  $filename = $tmpurlfile;
		  $tmpfiles{$tmpurlfile}++;
		  # Cachefile erstellen
		  if ($use_wwwcache && $o->can('cache')) {
		      my $cachefile = $o->cache($mapx, $mapy, 1);
		      if ($cachefile) {
			  copy($filename, $cachefile);
		      }
		  }
		  return 1;
	      }
	      return 0;
	  };

	  # XXX %tk_widget-Lösung funktioniert nicht optimal
	  # besser mit fork, auch unter Win32, arbeiten!
	  # (aber zunächst unter win98 und winnt austesten!)
	  my $get_by_http_pm = sub {
	      if ($do_wwwmap && $o->can('url')) {
		  eval { require Http };
		  if ($@) {
		      status_message($@, 'warn') if $verbose;
		      return; # wirklich kein Erfolg
		  }
		  local $Http::tk_widget = $top;
		  local $Http::timeout = 4;
		  local $Http::user_agent = ["Mozilla/4.78 [de] (WinNT; U)",
					     "Mozilla/4.75 [de] (Win98)",
					     "Mozilla/4.77 (Win95)",
					    ]->[rand(3)];
		  my %res;
		  my $tmpurlfile;

		  my $requrl = $o->url($mapx, $mapy);
		  return 0 if !defined $requrl;

		  # Popping up transient toplevels are bugging ---
		  # therefore a place()d frame is used.
		  my $abort_w = $top->Frame->place(-relx => 0.5, -rely => 0.5);
		  $abort_w->Button(-text => M"Abort WWW",
				   -command => sub {
				       $abortWWW = -1;
				   })->pack;

		  IncBusy($top);
		  eval {

		      $abort_w->raise if $abort_w; # over InputO widget

		      $tmpurlfile = "$tmpdir/bbbike_url.$$";
		      open(WWW, ">$tmpurlfile") or
			  die Mfmt("Kann auf die Datei %s nicht schreiben: %s", $tmpurlfile, $!);
		      binmode WWW;
		      print STDERR
			  Mfmt("Die URL <%s> wird geholt (Http.pm) ... ",
			       $requrl);
		      $abortWWW = 0;
		      %res = Http::get("url" => $requrl,
				       ($proxy ? ("proxy" => $proxy) : ()),
				       "debug" => $verbose,
				       "waitVariable" => \$abortWWW,
				      );
		      if ($res{"error"} == 200 && $abortWWW != -1) {
			  print WWW $res{"content"};
		      }
		      close WWW;
		  };
		  DecBusy($top);

		  $abort_w->destroy if $abort_w && Tk::Exists($abort_w);

		  if ($res{"error"} != 200) {
		      status_message(Mfmt("Fehler beim Holen der URL %s",
					  $requrl));
		      return; # kein Erfolg
		  }
		  if ($abortWWW == -1) {
		      status_message(Mfmt("Benutzerabbruch beim Holen der URL %s",
					  $requrl));
		      return; # kein Erfolg
		  }
		  print STDERR "OK\n";
		  $filename = $tmpurlfile;
		  $tmpfiles{$tmpurlfile}++;
		  # Cachefile erstellen
		  if ($use_wwwcache && $o->can('cache')) {
		      my $cachefile = $o->cache($mapx, $mapy, 1);
		      if ($cachefile) {
			  copy($filename, $cachefile);
		      }
		  }
		  return 1;
	      }
	      return 0;
	  };

	  if (!$get_by_http_pm->()) {
	      undef $filename; # sicherheitshalber
	      status_message(M"Fehler bei der WWW-Verbindung");
	  }
## Http.pm has better Tk support than LWP::UserAgent
#  	      ||
#  	  $get_by_lwp->()
#  	      ;
    }
    if (!defined $filename) {
	$filename = $o->filename($mapx, $mapy);
    }
    $filename;
}

### AutoLoad Sub
sub BBBikeHeavy::get_user_agent {
    return $ua if defined $ua;
    eval { require LWP::UserAgent };
    return undef if $@;
    $ua = LWP::UserAgent->new;
    $ua->agent("$progname/$VERSION");
    $ua->timeout(30);
    $ua->env_proxy;
    if ($os eq 'win' && eval { require Win32Util; 1 }) {
	Win32Util::lwp_auto_proxy($ua);
    }
    if ($proxy) {
	$ua->proxy(['http','ftp'], $proxy);
    }
    $ua;
}

### AutoLoad Sub
sub BBBikeHeavy::delete_map {
    $c->delete('map');
    if (defined $map_img) {
	eval { $map_img->delete };
	undef $map_img;
    }
    if (@map_surround_img) {
	foreach (@map_surround_img) {
	    eval { $_->delete }; # möglicherweise nicht mehr vorhanden
	}
	undef @map_surround_img;
    }
}

# Gibt eine Meldung aus, wie Module nachinstalliert werden können.
### AutoLoad Sub
sub BBBikeHeavy::perlmod_install_advice {
    my(@mod) = @_;
    @mod = grep { !exists $perlmod_install_advice_seen{$_} } @mod;
    return if !@mod;
    $perlmod_install_advice_seen{$_}++ for (@mod);
    if ($auto_install_cpan) {
	require AutoInstall::Tk;
	my $r = AutoInstall::Tk::do_autoinstall_tk(@mod);
	if ($r > 0) {
	    for my $mod (@mod) {
		warn "Re-require $mod...\n";
		eval "require $mod";
		die __LINE__ . ": $@" if $@;
	    }
	}
    } else {
	my $shell = ($os eq 'win' ? M"Eingabeaufforderung" : M"Shell");
	status_message
	    (
	     Mfmt((@mod > 1
		   ? "Die fehlenden Perl-Module können aus der %s mit dem Kommando\n"
		   : "Das fehlende Perl-Modul kann aus der %s mit dem Kommando\n"), $shell) .
	     ($os eq 'win' ?
	      "    ppm\n" .
	      "    " . join("\n    ", map { "install $_" } @mod) . "\n" .
	      "    quit\n" :
	      "    perl -MCPAN -e \"install " . join(", ", @mod) . "\"\n") .
	     M"aus dem Internet geholt und installiert werden.\n",

	     "err");
	0;
    }
}

### AutoLoad Sub
sub BBBikeHeavy::pdf_export {
    my(%args) = @_;

    # XXX A better solution would be some kind of "can_handle_imagetype"
    # method in BBBikeDraw.pm. This would return false and a list of
    # all missing modules, or "true" if everything's ok
    if (!eval { require PDF::Create; 1 }) {
	status_message("PDF::Create is not available", "warn");
	# XXX This is not exactly true --- the necessary PDF::Create
	# version is only available at sourceforge
	perlmod_install_advice("PDF::Create");
	return 1;
    }

    $args{-ext} = ".pdf";
    $args{-imagetype} = "pdf";
    BBBikeHeavy::any_bbbikedraw_export(%args);
}

### AutoLoad Sub
sub BBBikeHeavy::svg_export {
    my(%args) = @_;

    # XXX see above
    if (!eval { require SVG; 1 }) {
	status_message("SVG is not available", "warn");
	perlmod_install_advice("SVG");
	return 1;
    }

    $args{-ext} = ".svg";
    $args{-imagetype} = "svg";
    BBBikeHeavy::any_bbbikedraw_export(%args);
}



# any export via BBBikeDraw
### AutoLoad Sub
sub BBBikeHeavy::any_bbbikedraw_export {
    my(%args) = @_;
    my $use_visible_map = $args{-visiblemap} || !@realcoords;
    my $file = $args{-file} || $top->getSaveFile(-defaultextension => $args{'-ext'});
    my $geometry = $args{-geometry} || "auto";
    return unless defined $file;
    require BBBikeDraw;
    open(OUT, ">$file") or
	status_message
	    (Mfmt("Kann auf %s nicht schreiben: %s",
		  $file, $!));

    my $scope = 'city';
    if ($str_file{'l'}) {
	if ($str_far_away{'l'}) {
	    $scope = 'wideregion';
	} else {
	    # XXX str_regions (Sachsen-Anhalt etc.)???
	    $scope = 'region';
	}
    }

    my @draw = ('title');
    push @draw, 'ampel' if ($p_draw{'lsa'});
    push @draw, 'berlin' if ($p_draw{'g'});
    push @draw, 'potsdam' if ($p_draw{'gP'});
    push @draw, 'deutschland' if ($p_draw{'gD'});
    push @draw, 'wasser' if ($str_draw{'w'});
    push @draw, 'flaechen' if ($str_draw{'f'});
    push @draw, 'ubahn' if ($str_draw{'u'});
    push @draw, 'sbahn' if ($str_draw{'b'});
    push @draw, 'rbahn' if ($str_draw{'r'});
    push @draw, 'str' if ($str_draw{'s'});
    push @draw, 'ort' if ($p_draw{'o'});
    push @draw, 'wind';
    push @draw, 'strname' if ($str_name_draw{'s'});
    push @draw, 'ubahnname' if ($str_name_draw{'u'});
    push @draw, 'sbahnname' if ($str_name_draw{'s'});

    IncBusy($top);
    eval {
	my $draw = BBBikeDraw->new
	    (ImageType => $args{-imagetype},
	     Module => $args{-module},
	     Coords => [map { join ",", @$_ } @realcoords],
	     Fh => \*OUT,
	     Scope => $scope,
	     Geometry => $geometry, # landscape or portrait
	     Draw => [@draw],
	     NoInit => 1,
	     ($net ? (MakeNet => sub { $net }) : ()),
	    );
	if ($use_visible_map) {
	    # use visible map for bounding box
	    my($minx,$miny,$maxx,$maxy) = $c->get_corners;
	    ($minx,$miny) = anti_transpose($minx,$miny);
	    ($maxx,$maxy) = anti_transpose($maxx,$maxy);
	    $draw->set_bbox($minx,$miny,$maxx,$maxy);
	    $draw->init;
	    $draw->create_transpose;
	} else {
	    # else use bounding box of route
	    $draw->init;
	    $draw->pre_draw;
	}
	$draw->draw_map if $draw->can("draw_map");
	if (@realcoords) {
	    $draw->draw_route if $draw->can("draw_route");
	    if ($net && $draw->can("add_route_descr")) {
		$draw->add_route_descr(-net => $net)
	    }
	}
	$draw->flush;
    };
    my $err = $@;
    DecBusy($top);
    close OUT;
    if ($err) {
	unlink $file;
	status_message(Mfmt("Die %s-Datei konnte nicht erstellt werden. Grund: %s", $args{-imagetype}, $err), "error");
	return;
    }
}

######################################################################
# Zeigt den Dialog mit den Routen-Registern an.
### AutoLoad Sub
sub BBBikeHeavy::show_register {
    my $t = redisplay_top($top, M"Register",
			  -title => M"Routen-Register");
    return if !defined $t;

    $top->Advertise(RegisterWindow => $t);

    $t->protocol('WM_DELETE_WINDOW', sub { $t->withdraw });

    $t->Label(-text => M("Abspeichern").":")->grid(-row => 0,
						   -column => 0,
						   -sticky => 'w');
    $t->Label(-text => M("Anzeigen").":")->grid(-row => 1,
						-column => 0,
						-sticky => 'w');
    $t->Checkbutton(-text => M("Karte zentrieren"),
		    -variable => \$register_window_adjust)
	->grid(-row => 2, -column => 0, -sticky => "w", -columnspan => 2);
    my $sf = $t->Frame->grid(-row => 0, -column => 1,
			     -sticky => 'w');
    my $rf = $t->Frame->grid(-row => 1, -column => 1,
			     -sticky => 'w');

    if ($advanced) {
	# XXX there are still some issues (see below)
	$t->Button(-text => M("In Datei speichern"),
		   -command => \&save_register_routes,
		  )->grid(-row => 0,
			  -column => 2,
			  -sticky => 'we');
	$t->Button(-text => M("Von Datei laden"),
		   -command => \&load_register_routes,
		  )->grid(-row => 1,
			  -column => 2,
			  -sticky => 'we');
    }

    my @ret_b;
    foreach my $i (0 .. 9) {
	my $ii = $i;
	my $text = (!$ii ? M"Undo" : $ii);
	my $b;
	$b = $sf->Button
	    (-text => $text,
	     -command => sub {
		 if ($save_route{$ii}) {
		     require Tk::Dialog;
		     if ($top->Dialog
			 (-text    => M"Gespeicherte Route ersetzen?",
			  -buttons => [M"Ja", M"Nein"],
			  -default_button => M"Nein",
			 )->Show ne M"Ja") {
			 return;
		     }
		 }
		 save_route_to_register($ii);
		 $ret_b[$ii]->configure(-fg => 'red');
		 $b->configure(-fg => 'red');

		 if ($balloon) {
		     my $text = get_route_description();
		     if ($text ne '') {
			 foreach ($b, $ret_b[$ii]) {
			     $balloon->attach($_, -msg => $text);
			 }
		     }
		 }
	     }
	    )->pack(-side => 'left');
	$ret_b[$i] = $rf->Button
	    (-text => $text,
	     -command => sub {
		 if (get_route_from_register($ii)) {
		     center_whole_route() if $register_window_adjust;
		 } else {
		     $top->messageBox
			 (-title => M"Leeres Register",
			  -text => M"Hier ist keine Route gespeichert.",
			  -icon => 'error',
			  -type => 'Ok');
		 }
	     }
	    )->pack(-side => 'left');
	$t->Advertise("SaveButton_" . $i => $b);
	$t->Advertise("LoadButton_" . $i => $ret_b[$i]);
    }
}

# XXX evtl. croaks on not defined added points in net
# XXX bikepwr values are not valid in new session
# XXX really use .bbrs extension?
### AutoLoad Sub
sub BBBikeHeavy::save_register_routes {
    my $f = $top->getSaveFile(-defaultextension => ".bbrs");
    return unless $f;
    open(F, ">$f") or die Mfmt("Schreiben auf <%s> nicht möglich: %s", $f, $!);
    require Data::Dumper;
    print F Data::Dumper->new([\%save_route], ['*save_route'])->Indent(0)->Dump;
    close F;
}

### AutoLoad Sub
sub BBBikeHeavy::load_register_routes {
    my $f = $top->getOpenFile(-title => M"Register laden",
			      -filetypes => [['Register-Datei' => '.bbrs'],
					     ['Alle Dateien' => '*']],
			     );
    return unless $f;
    require Safe;
    my $s = Safe->new;
    my $reg_t = $top->Subwidget("RegisterWindow");
    if ($reg_t) {
	foreach my $b ($reg_t->Subwidget) {
	    $b->configure(-fg => "black"); # XXX do not hardcode?
	}
    }
    $s->share(qw(%save_route));
    $s->rdo($f);
    if ($reg_t) {
	my $i = 1;
	while(my $b = $reg_t->Subwidget("SaveButton_" . $i)) {
	    if ($save_route{$i}) {
		$b->configure(-fg => "red");
		if (my $b2 = $reg_t->Subwidget("LoadButton_" . $i)) {
		    $b2->configure(-fg => "red");
		}
	    }
	    $i++;
	}
    }
}

######################################################################
### Kalorien-Fenster
### AutoLoad Sub
sub BBBikeHeavy::show_calories {
    if (!$show_calories) {
	if (Tk::Exists($toplevel{Calories})) {
	    $toplevel{Calories}->destroy;
	}
	return;
    }

    my $t = redisplay_top($top, 'Calories',
			  -title => M"Kalorienverbrauch");
    return if !defined $t;
    my $withdraw_sub = sub { $t->destroy;
			     $show_calories = 0 };
    $t->protocol('WM_DELETE_WINDOW', $withdraw_sub);
    $t->Label(-text => M"Kalorienverbrauch")->pack;
    my $f = $t->Frame->pack;
    foreach my $def ([\@power, 'W:', \@calories_power, 'kcal'],
		     #[\@speed, 'km/h:', \@calories_speed, 'kcal'],
		    ) {
	for(my $i=0; $i<=$#power; $i++) {
	    $f->Label(-textvariable => \$def->[0][$i]
		 )->grid(-row => $i, -column => 0, -sticky => 'e');
	    $f->Label(-text => $def->[1]
		     )->grid(-row => $i, -column => 1, -sticky => 'w');
	    $f->Label(-textvariable => \$def->[2][$i]
		     )->grid(-row => $i, -column => 2, -sticky => 'e');
	    $f->Label(-text => $def->[3]
		     )->grid(-row => $i, -column => 3, -sticky => 'w');
	}
    }
}

# This is not executed by default anymore. Somewhere in FreeBSD 2 or
# 3, there used to be a datasize limit by the default login.conf. See
# http://www.freebsd.org/cgi/cvsweb.cgi/src/etc/login.conf for a
# history. This limit was removed in revision 1.21 of this file. I
# don't know of any other OSes with default resource limits.
sub BBBikeHeavy::check_available_memory {
    if ($os eq 'unix') {
	# Check for limited resources.
	my $soft_data;
	eval {
	    local $^W = 0;
	    require BSD::Resource; # can't be autoused
	    ($soft_data) = BSD::Resource::getrlimit(&BSD::Resource::RLIMIT_DATA);
	    $soft_data /= 1024 if $soft_data != -1;
	};
	if ($@ && $^O =~ /bsd/) {
	    # There's a bug in freebsd4.3 limits issuing a warning. Ignore it.
	    open(L, "limits -d|");
	    while (<L>) {
		if (/datasize(?:-cur)?\s+(\d+)/) {
		    $soft_data = $1;
		    last;
		}
	    }
	    close L;
	}
	if (defined $soft_data &&
	    $soft_data != -1 &&
	    $soft_data < MINMEM) {
	    warn
		Mfmt("
Achtung:

 Der frei verfügbare Speicherplatz ist durch resource limits auf %s kB
 eingeschränkt worden. Empfohlen sind %s kB. In der csh/tcsh kann
 man die Einschränkung mit `limit datasize unlimited' aufheben, in der sh/bash
 mit `ulimit -d %s`. Zusätzlich muss man evtl. seine Login-Klasse
 ändern oder die Werte in /etc/login.conf erhöhen.

 Siehe auch limits(1) und login.conf(5).

", $soft_data, MINMEM, MINMEM);
	}
    }
}

### AutoLoad Sub
sub BBBikeHeavy::reload_all {
    if ($BBBikeLazy::mode) {
	bbbikelazy_reload();
    }

    my @changed_files;

    my %change;
    foreach my $type (keys %str_obj) {
	my $o = $str_obj{$type};
	next if !$o;
	if (!$o->is_current) {
	    $o->reload;
	    $change{"str"}->{$type} = 1;
	    push @changed_files, $o->dependent_files;
	}
    }
    foreach my $type (keys %p_obj) {
	my $o = $p_obj{$type};
	warn "Should not happen: No object for point type $type", next if !$o;
	if (!$o->is_current) {
	    $o->reload;
	    $change{"p"}->{$type} = 1;
	    push @changed_files, $o->dependent_files;
	}
    }

    # Special handling for hoehe layers
    if ($change{"p"}->{"hoehe"}) {
	read_hoehe(-force => 1);
    }

    $progress->InitGroup;
    while(my($linetype, $v) = each %change) {
	while(my($type, $vv) = each %$v) {
	    if ($verbose) {
		warn "Updating $linetype $type ...\n";
	    }
	    plot($linetype,$type, FastUpdate => 1);
	}
    }
    $progress->FinishGroup;

    # Need to delete comments_net?
    if ($comments_net) {
	if (exists $change{str}->{comm}) {
	    undef $comments_net;
	} else {
	    for my $src ($comments_net->sourceobjects) {
		if (!$src->is_current) {
		    undef $comments_net;
		    last;
		}
	    }
	}
    }

    if (!$edit_mode_flag) { # be fast in edit mode, do not rebuild net
	my $need_to_rebuild_net = 0;
	if ($net) {
	    my %changed_files = map {($_,1)} @changed_files;
	    for my $net_file ($net->sourcefiles) {
		if (exists $changed_files{$net_file}) {
		    $need_to_rebuild_net = 1;
		    last;
		}
	    }
	}
	if ($need_to_rebuild_net) {
	    make_net();
	}
    }
    
}

1;

__END__
