Source file: /~heha/hs/Funkuhr.zip/src/Karte.c

/********************************
 * Projekt: Funkuhr DCF77	*
 * Europakartendarstellung	*
 * zur Vorhersageregionsauswahl	*
 * und Wetterkarte wie im TV	*
 ********************************/
#include "Funkuhr.h"
#include <math.h>

#include <olectl.h>


#define SETMIN(a,b) if ((a)>(b)) (a)=(b)
#define SETMAX(a,b) if ((a)<(b)) (a)=(b)

void InflateRectForPoint(RECT*r, const POINT*p) {
 SETMIN(r->left  ,p->x);
 SETMIN(r->top   ,p->y);
 SETMAX(r->right ,p->x);
 SETMAX(r->bottom,p->y);
}

typedef struct{
 int Z,N;
}scale_t;

HWND hFirst;
const TCHAR KARTECLASSNAME[]=T("HSW");
#ifndef UNICODE
static bool NoAlpha;	// Win98-Hack
#endif

// Speicher-Daten, in Karten-Pixel umgerechnet
typedef struct{
 int nPoly;		// Anzahl Poly[]-Punkte
 char CitySize;		// Bezugsort-Größe aus Ressource, nach Einwohnern, bspw. 10 = 1-2 Mio
 //char TextPos;	// Vorschlag für Text um die Stadt herum, 0 = Nord, 1=NO, 2=Ost usw.
 //char rsv[2];
 BYTE fix;		// Bit 0 = rcText ist festgelegt, unverschieblich
			// Bit 1 = rcSymb ist festgelegt, unverschieblich
 POINT CityPos;		// Vorhersage-Bezugsort, in Bitmap-Pixel
// Die folgenden 4 Rechtecke liegen in unverschobenen Client-Koordinaten vor,
// ändern sich also mit der Skalierung oder dem Hot-Tracking, aber nicht beim Scrollen,
// außer wenn sich Texte oder Wettersymbole nach außerhalb bewegen
 RECT rcCity;		// Platz für Stadtklecks
 RECT rcText;		// okkupierter Platz für den Text
 RECT rcSymb;		// okkupierter Platz für's Wettersymbol
 RECT rcPoly;		// reichliches Umfangsrechteck für Polygon (u.a. darf da das Wettersymbol nicht raus)
 POINT Centroid;	// Schwerpunkt der Polygonfläche, in Bitmap-Pixel
 int Area;		// vorzeichenbereinigter Flächeninhalt der Polygonfläche, in Bitmap-Quadratpixel
 int rsv1;
 POINT Poly[];
}MAPMEM;

typedef struct{		// "Expandierte" Grenzen-Koordinaten
 union{
  struct{
   WORD n;		// Anzahl Linien-Punkte, >=2, 0 = Ende der Liste
   char staaten[2];	// Staat links/rechts
  };
  DWORD hdr;		// Header genauso wie in Ressource
  BOOL valid;		// Endekennung (wenn obige Angaben Null sind)
 };
 POINT points[];	// Punktliste in Bitmap-Pixel
}BORDEREX;

static BORDEREX*IterateBorderex(const BORDEREX*bo) {
 if (bo) (BYTE*)bo+=sizeof(BORDEREX)+bo->n*sizeof(POINT);
 return (BORDEREX*)bo;
}

// Karten-Info (also je nach Bitmap oder Vektorgrafik)
// … so was ähnliches wie eine Virtuelle Methodentabelle
typedef struct _KINFO{
 PCTSTR name;		// ist ohnehin hartkodiert! NULL für "leeres" KINFO
 SIZE szBitmap;		// für EraseBkgnd (nicht diesen Bereich löschen, Flackern vermeiden, {0,0} bei WMF)
 PointF (*kMap)(PointF);	// Umrechnungsprozedur Länge/Breite in Pixel
 PointF (*kUnmap)(PointF);	// Umrechnungsprozedur Pixel in Länge/Breite
 void (*kGradnetz)(const struct _KINFO*,HDC dc);	// Gradnetz zeichnen (je nach Projektionsart)
}KINFO;

// so 'ne Art Objekt
typedef struct{
 HWND hMain,hMap,hMini,hToolbar,hStatus,hTip;	// Fenster-Handles, hMap kann gleich hMain sein, beide sind niemals 0
 const KINFO *kInfo;	// [18] kein NULL-Pointer
 POINT MousePos;	// [1C] Mausposition in hMap-Client-Koordinaten (hMini muss extra geklärt werden!)
 char HoverIndex;	// [24] -1..90
 BYTE kShow;		// [25] 6 lokale Config-Kopien
 char kMini;
 BYTE kVis;
 char kScale;
 BYTE kAutoScale;
 BYTE kWetter;
 char HoverStaat;	// [2B] -1(Meer) .. 46 (Kosovo)
 bool bShifted:1;	// [2C]	Geschoben mit linker Maustaste? Wenn nicht beim Loslassen Config.Region setzen
 bool bFromMouse:1;	//	Mausereignis? Mausposition ams Zentrum benutzen. Sonst Zentrum = Client-Mitte
 bool bInitialized:1;	//	<map> und <gdi> initialisiert
 bool bPictureHBM:1;	// hPicture ist statt pPicture gültig
 bool bReserved:4;
 BYTE Recursion;	// [2D]	Rekursionszähler für WM_SIZE
 char MiniScale;	// [2E] Standard: ~8 (Von der Bildgröße und Bildschirmgröße abhängig.)
 char MyPosStaat;	// [2F] -1..NUMSTAAT-1 = Hittest-Ergebnis für "meine Position"
 SCROLLINFO ScrollInfo[2];//[30] Die großen Batzen hinten (Hilft das?)
 RECT ScrollRect;	// [68] Client-Bereich ohne Toolbar und Statuszeile (und ohne die Scrollbalken??)
 scale_t Scale;		// [78]
 RECT MapExtent;	// [80] Größe der Map oder des Hintergrundbildes (für Rollbalken), unskaliert
 union{
  IPicture *pPicture;	// [90]
  HBITMAP hPicture;
 };
 POINT kMiniPos;	// [94]	Position des freien Miniatur-Fensters, in Bildschirmkoordinaten
 HBITMAP MiniBmp;	// [9C]	Cache der Bitmap des Hintergrunsbildes für das Mini-Fenster
 HIMAGELIST flags;	// [A0]
 MAPMEM *map[91];	// [A4]	Vorhersageregionen, Koordinaten auf pPicture umgerechnet
 HRGN staat[NUMSTAAT];	//[210]	Vorgehaltene Regionen für Treffertests und zum Ausmalen
 TCHAR strTip[256];	//[32C]	Vorgehaltener fliegender Text
 TCHAR strStatus4[128];	//[52C]	Vorgehaltener Ownerdraw-Text für Status-Feld 4
 BORDEREX bo/*[]*/;	//[62C] Vorgehaltene Punktliste der Küsten und Grenzen, pPicture-Koordinaten
}karte;

static void LoadClientRect(HWND Wnd, RECT *rc) {
 static const char ControlsList[]={0,0,true,10,true,11,false};
 int Controls[elemof(ControlsList)];
 bloat(Controls,ControlsList,elemof(Controls));
 GetEffectiveClientRect(Wnd,rc,Controls);
}

// "vormultipliziertes" RGB+Alpha-Quadrupel erzeugen
#define RGBA(r,g,b,a) (COLORREF)MAKELONG(MAKEWORD(r*(a)/255,g*(a)/255),MAKEWORD(b*(a)/255,a))

static IPicture* LoadPictureFile(LPCTSTR szFile) {
 IPicture *ret=NULL;
 IStream *pstm;
 if (SUCCEEDED(SHCreateStreamOnFile(szFile,0,&pstm)) && pstm) {
  if (!SUCCEEDED(OleLoadPicture(pstm,0,FALSE,&IID_IPicture,&ret))) ret=NULL;
  (*(IStreamVtbl**)pstm)->Release(pstm);
 }
 return ret;
}

/*************************************************
 **	Küsten- und Grenzverläufe (Eigenbau)	**
 *************************************************/
typedef struct{
 char x,y;
}POINTC;

typedef struct{
 union{
  struct{
   WORD n;		// Anzahl Punkte insgesamt, 0 = Listen-Ende
   char staaten[2];	// Staat auf linker/rechter Seite der Grenze
  };
  DWORD hdr;
 };
 POINTS start;		// Startkoordinate, Insel/Exklave wenn gleich Ende
 POINTC Diff[];		// n-1 Punkt-Differenzen
}BORDER;

// Border-Zeiger um <index> Einträge vorrücken
static const BORDER *IterateBorder(const BORDER*bo) {
 if (bo) (BYTE*)bo+=sizeof(BORDER)+(bo->n-1)*sizeof(POINTC);
 return bo;
}

// Borderdaten beschaffen
static const BORDER *GetBorder() {
 static HGLOBAL bo;	// Ressourcen-Daten, durch valid=0 beendet
 if (!bo) bo=LoadResource(0,FindResource(0,MAKEINTRESOURCE(49),RT_RCDATA));
 return LockResource(bo);
}

// Berechnet Platz für LocalAlloc()
static int CalcBorderexSize() {
 int ret=4;		// für die Ende-Kennung
 const BORDER*bo;
 for (bo=GetBorder(); bo->n; bo=IterateBorder(bo)) {
  ret+=sizeof(BORDEREX)+bo->n*sizeof(POINT);
 }
 return ret;
}

static bool ComparePoint(const POINT*p1, const POINT*p2) {
 return abs(p1->x-p2->x)<3 && abs(p1->y-p2->y)<3;
}

typedef struct _POLYJOIN {	// repräsentiert ein Linienstück
 struct _POLYJOIN *next, *prev;
 const BORDEREX*bo;
 bool reverse;		// wenn das Land rechtsseitig ist
}POLYJOIN;

typedef struct _POLYSEARCH {	// repräsentiert ein Polygon
 struct _POLYSEARCH *next, *prev;
 const POINT *A, *E;	// derzeitiger Anfangs- und Endpunkt
 POLYJOIN *a, *e;	// die beiden Enden der POLYJOIN-Liste
}POLYSEARCH;

// + = hinten, - = vorn
// rechts = Landseite der Grenze <bo>, false=links
static char CanAttach(const POLYSEARCH*ps, const BORDEREX*bo, bool reverse) {
// if (ComparePoint(ps->enden+0,ps->enden+1)) return 0;	// Geschlossene Figur
 if (!reverse && ComparePoint(ps->E,bo->points)) return 1;		// vorwärts am Ende
 if (reverse && ComparePoint(ps->E,bo->points+bo->n-1)) return 1;	// rückwärts am Ende
 if (!reverse && ComparePoint(ps->A,bo->points+bo->n-1)) return -1;	// vorwärts am Anfang
 if (reverse && ComparePoint(ps->A,bo->points)) return -1;		// rückwärts am Anfang
 return 0;
}

static void CreateStaatenRegions(karte*K) {
 char staat;
// Ich erspare mir den Ärger, den Datenbaum einzeln abräumen zu müssen
 for (staat=0; staat<NUMSTAAT; staat++) {
// 1. Polygone und Punkte für das Polygon zählen
  HANDLE heap=HeapCreate(HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE,1,0);
  POLYSEARCH*list=NULL;
  const BORDEREX*bo=&K->bo;
  for(;bo->valid;bo=IterateBorderex(bo)) {
   POLYSEARCH*ps;
   bool reverse=bo->staaten[1]==staat;
   if (reverse || bo->staaten[0]==staat) {
    POLYJOIN*pj=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,sizeof*pj);
    pj->bo=bo;
    pj->reverse=reverse;
    for (ps=list; ps; ps=ps->next) {
     char a=CanAttach(ps,bo,reverse);
     if (a) {
      POLYSEARCH*ps2;
      if (a<0) {	// vorn
       ps->a->prev=pj;
       pj->next=ps->a;
       ps->A=&bo->points[reverse?bo->n-1:0];
       ps->a=pj;
      }else{		// hinten
       ps->e->next=pj;
       pj->prev=ps->e;
       ps->E=&bo->points[reverse?0:bo->n-1];
       ps->e=pj;
      }
      for(a=0, ps2=ps->next; ps2; ps2=ps2->next) {	// Freie Stücken anketten, wenn's passt
       if (ComparePoint(ps->E,ps2->A)) a=1;		// ps2 passt hinten dran
       else if (ComparePoint(ps->A,ps2->E)) a=-1;	// ps2 passt vorn dran
       if (a) {
        if (ps2->next) ps2->next->prev=ps2->prev;	// ps2 aus Liste ausketten
	if (ps2->prev) ps2->prev->next=ps2->next;
	if (a<0) {			// an ps vorn anketten
	 ps2->e->next=ps->a;		// Sub-Kette um-hängen
	 ps->a->prev=ps2->e;
	 ps->a=ps2->a;
	 ps->A=ps2->A;
	}else{				// an ps hinten anketten
	 ps2->a->prev=ps->e;
	 ps->e->next=ps2->a;
	 ps->e=ps2->e;
	 ps->E=ps2->E;
	}		// Jetzt könnte man ein HeapFree() machen...
        break;
       }
      }
      goto brk;
     }
    }
// Ein neues Polygon beginnen
    ps=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,sizeof*ps);
    if (list) list->prev=ps;
    ps->next=list;
    list=ps;
    ps->A=&bo->points[reverse?bo->n-1:0];
    ps->E=&bo->points[reverse?0:bo->n-1];	// das gesuchte Land sei im Normalfall _links_
    ps->a=ps->e=pj;
brk:;
   }//if (Staatsgrenze passt)
  }//for (alle Grenzlinien)
// Nun sollten im Idealfall ein oder mehrere (Inseln, Seen) _geschlossene_ (*A==*E) Polygone übrig bleiben.
// Bei offenem Polygon wird mit einer Geraden geschlossen (Russland).
  if (list) {
   int ip=0,ig=0,*ii,*il;
   POINT*pi,*pl;
   const POLYSEARCH*ps;
   for (ps=list;ps;ps=ps->next) {
    POLYJOIN*pj;
    ip++;						// Polygone zählen
    for (pj=ps->a; pj; pj=pj->next)
    ig+=pj->bo->n;					// Gesamtpunkte zählen
   }
   il=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,ip*sizeof*il);	//**-Crypt??
   pl=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,ig*sizeof*pl);
   for (ii=il,pi=pl,ps=list;ps;ii++,ps=ps->next) {	// Punkte zusammenkopieren
    POLYJOIN*pj;
    for (pj=ps->a; pj; pj=pj->next) {
     *ii+=pj->bo->n;					// Doppelpunkte ignorieren
     if (pj->reverse) {
      int i;
      for (i=0; i<pj->bo->n; i++) *pi++=pj->bo->points[pj->bo->n-1-i];	// Punktreihenfolge umkehren
     }else{
      __movsd((DWORD*)pi,(DWORD*)pj->bo->points,pj->bo->n*sizeof*pi/sizeof(DWORD));
      pi+=pj->bo->n;					// Punktreihenfolge belassen
     }
    }
   }
   K->staat[staat]=CreatePolyPolygonRgn(pl,il,ip,ALTERNATE);
  }
  HeapDestroy(heap);
 }
}

/*************************************************
 **	Google Maps Overlay	(meteotime.com)	**
 *************************************************/
// Ressourcen-Daten, in Mercator-Pixeln 
typedef struct{
 BYTE nPoly;
 BYTE CitySize;	// High-Nibble = Beschriftungs-Position (N, NO, O usw.)
 POINTS CityPos;
 POINTC PolyDiff[];
}MAPDATA;

// Info für Mercatorprojektion (der Ressourcen-Polygone)
const FLOAT Aequator=2105;	// Pixel-Position
const FLOAT Nullmeridian=10;	// Pixel-Position
const FLOAT PixPerGrad=22.78f;	// Pixel pro Grad

// Berechnet Integral von 0 bis x über dt/cosh(t), im Gradmaß, liefert Gradmaß
// Zur Umrechnung von Karten-"Grad" in Breitengrad
static FLOAT gudermann(FLOAT x) {
 return (FLOAT)((2*atan(exp(x*PI180))-PI/2)/PI180);
}

static PointF UnmapMercator(PointF pt) {	// Umrechnung
 pt.X = (pt.X-Nullmeridian)/PixPerGrad;
 pt.Y = gudermann((Aequator-pt.Y)/PixPerGrad);
 return pt;
}

static PointF MapMercator(PointF pt) {
 pt.X = Nullmeridian+pt.X*PixPerGrad;
 pt.Y = (float)(Aequator-log(tan(PI/4 + pt.Y*PI180/2))/PI180*PixPerGrad);
 return pt;
}

static const FLOAT GradnetzX=5;
static const FLOAT GradnetzY=5;	// alle 5 Grad eine Gerade / ein Kreis

// Gradnetz für Mercatorprojektion zeichnen (nur Geraden)
// Für Europa sinnvoller Bereich: x=-40°..90°, y=30°..80°
// DCF77-Empfangsgebiet: x=-10°..30°, y=35°..65°
// Geoposition des Senders: x=50°01', y=9°00'
// TODO: Entfernungsberechnung und Ausgabe (km/mi/ms) irgendwo
static void DrawMercatorNetz(const KINFO*K, HDC dc) {
 PointF tl={-180,80},br={180,0},mi={12,42};
 WCHAR s[10];
 FLOAT f;
 int i;
 tl=K->kMap(tl); br=K->kMap(br); mi=K->kMap(mi);
 for (f=30; f<=80; f+=GradnetzY) {
  PointF w={0,f};
  w=K->kMap(w);
  i=lrint(w.Y);
  Line(dc,lrint(tl.X),i,lrint(br.X),i);
  TextOutW(dc,lrint(mi.X),i+2,s,wnsprintfW(s,elemof(s),L"%d",lrint(f)));
 }
 for (f=-40; f<=90; f+=GradnetzX) {
  PointF w={f,0};
  w=K->kMap(w);
  i=lrint(w.X);
  Line(dc,i,lrint(tl.Y),i,lrint(br.Y));
  TextOutW(dc,i+2,lrint(mi.Y),s,wnsprintfW(s,elemof(s),L"%d",lrint(f)));
 }
}

/*********************************************************
 **	„eclipse-DCF77-Wetter.jpg“	(meteotime.com)	**
 *********************************************************/
// Umrechnung für Kegelprojektion der o.g. Datei (keine echte Kegelprojektion!!)
#define PolX 724.5f		// Pol: Pixelposition
#define PolY -1248		// Pol: Pixelposition
#define GradKoeffX 0.8f		// Grad pro Grad (Berührkreis theoretisch arcsin(0.8)=53°)
#define GradKoeffY 42.25f	// Pixel pro Grad (Berührkreis ist dann wo?)
#define GradNull 10		// Senkrechter Meridian
#define GradPol 95.97f		// !=90 für nicht ganz echte Kegelprojektionen
#define Tangens (1.2f/180)	// Verzerr-Koeffizient, für echte Kegelprojektion PI/180
#define Beruehr 53		// Hier sind die Breitenkreise am engsten

// Aus Länge+Breite logische Pixelkoordinaten ausrechnen
static PointF MapKegel(PointF pt) {
 double a = (pt.X-GradNull)*GradKoeffX*PI180;	// Winkel in rad
 pt.Y = (float)(GradPol-tan((pt.Y-Beruehr)*Tangens)/Tangens-Beruehr)*GradKoeffY;	// Radius in Pixel
 pt.X = PolX+(float)sin(a)*pt.Y;
 pt.Y = PolY+(float)cos(a)*pt.Y;
 return pt;
}

// Aus logischen Pixelkoordinaten Länge+Breite berechnen
static PointF UnmapKegel(PointF pt) {
 double a,b;
 pt.X-=PolX; pt.Y-=PolY;
 a = atan2(pt.X,pt.Y)/PI180/GradKoeffX+GradNull;
 b = GradPol-hypotf(pt.X,pt.Y)/GradKoeffY-Beruehr;
 pt.Y = (float)atan(b*Tangens)/Tangens+Beruehr;
 pt.X = (float)a;
 return pt;
}
#undef Beruehr
#undef Tangens
#undef GradPol
#undef GradKoeffY
#undef GradKoeffX

/*****************************************
 **	„Europa.gif“	(wikimedia.org)	**
 **	„Europe_topography_map.png“	**
 *****************************************/
#define EuPolX 612
#define EuPolY -950
#define EuKoeffX 0.79f	// Grad pro Grad (Berührkreis theoretisch arcsin(0.8)=53°)
#define EuKoeffY 30.52f	// Pixel pro Grad (Berührkreis ist dann wo?)
#define EuNull 15		// Senkrechter Meridian
#define EuPol 97.1f	// !=90 für nicht ganz echte Kegelprojektionen
#define EuTangens (PI/180)	// Verzerr-Koeffizient, für echte Kegelprojektion PI/180
#define EuBeruehr 52		// Hier sind die Breitenkreise am engsten
#define EtCropX 24	// hier wurde Europa.gif herausgeschnitten (?)
#define EtCropY 223

static PointF MapEu(PointF pt) {
 double a = (pt.X-EuNull)*EuKoeffX*PI180;	// Winkel in rad
 pt.Y = (float)(EuPol-tan((pt.Y-EuBeruehr)*EuTangens)/EuTangens-EuBeruehr)*EuKoeffY;	// Radius in Pixel
 pt.X = EuPolX+(float)sin(a)*pt.Y;
 pt.Y = EuPolY+(float)cos(a)*pt.Y;
 return pt;
}
static PointF MapEt(PointF pt) {
 pt=MapEu(pt);
 pt.X+=EtCropX;
 pt.Y+=EtCropY;
 return pt;
}

// Aus logischen Pixelkoordinaten Länge+Breite berechnen
static PointF UnmapEu(PointF pt) {
 double a,b;
 pt.X-=EuPolX; pt.Y-=EuPolY;
 a = atan2(pt.X,pt.Y)/PI180/EuKoeffX+EuNull;
 b = EuPol-hypotf(pt.X,pt.Y)/EuKoeffY-EuBeruehr;
 pt.Y = (float)(atan(b*EuTangens)/EuTangens+EuBeruehr);
 pt.X = (float)a;
 return pt;
}
static PointF UnmapEt(PointF pt) {
 pt.X-=EtCropX;
 pt.Y-=EtCropY;
 return UnmapEu(pt);
}

#undef EuKoeffX
#undef EuKoeffY
#undef EuPol
#undef EuTangens
#undef EuBeruehr

static void DrawGradnetz(const KINFO*K, HDC dc);

static const KINFO KINFO_ECLIPSE={T("eclipse-DCF77-Wetter.jpg"), 1148,1290,MapKegel,UnmapKegel,DrawGradnetz};
static const KINFO KINFO_EUROPA ={T("Europa.gif"),		  957, 910,MapEu,   UnmapEu,   DrawGradnetz};
static const KINFO KINFO_EUROPE ={T("Europe_topography_map.png"),1475,1200,MapEt,   UnmapEt,   DrawGradnetz};
static const KINFO KINFO_NONE   ={NULL,0,0,MapMercator,UnmapMercator,DrawMercatorNetz};

static void DrawGradnetz(const KINFO*K, HDC dc) {
 FLOAT r,lng;
 PointF pol={PolX,PolY};
 WCHAR s[10];
 if (K!=&KINFO_ECLIPSE) {pol.X=EuPolX; pol.Y=EuPolY;}
 if (K==&KINFO_EUROPE) {pol.X+=EtCropX; pol.Y+=EtCropY;}
 for (r=30; r<=80; r+=GradnetzY) {
  FLOAT rr;
  PointF pt={GradNull,r};
  if (K!=&KINFO_ECLIPSE) pt.X=EuNull;
  pt=K->kMap(pt);
  rr=pt.Y-pol.Y;
  Ellipse(dc,lrint(pol.X-rr),lrint(pol.Y-rr),lrint(pol.X+rr),lrint(pol.Y+rr));
  TextOutW(dc,lrint(pt.X)+2,lrint(pt.Y),s,wnsprintfW(s,elemof(s),L"%d",lrint(r)));
 }
 for (lng=-40; lng<=90; lng+=GradnetzX) {
  PointF pp={lng,0};
  pp=K->kMap(pp);
  Line(dc,lrint(pol.X),lrint(pol.Y),lrint(pp.X),lrint(pp.Y));
  pp.X=lng;
  pp.Y=42;
  pp=K->kMap(pp);
  TextOutW(dc,lrint(pp.X)+1,lrint(pp.Y),s,wnsprintfW(s,elemof(s),L"%d",lrint(lng)));
  if (lng==10) SetTextAlign(dc,TA_LEFT|TA_BOTTOM);
 }
}
#undef EuPolX
#undef EuPolY
#undef GradNull
#undef PolX
#undef PolY
#undef EuNull
#undef EtCropX
#undef EtCropY
/*****************************************
 **  Ende kartenspezifischer Routinen	**
 *****************************************/

// MapExtent-Breite bzw. Höhe
static int MapExtentExt(const karte *K, int nBar) {
 return (&K->MapExtent.right)[nBar]-(&K->MapExtent.left)[nBar];
}
// ScrollRect-Breite bzw. Höhe
static int ScrollRectExt(const karte *K, int nBar) {
 return (&K->ScrollRect.right)[nBar]-(&K->ScrollRect.left)[nBar];
}

// Bitmap-Position skalieren
static int scale(const karte *K, int p) {
 return MulDiv(p,K->Scale.Z,K->Scale.N);
}
// Client-Pixel-Verschiebung
static int shift(const karte *K, int nBar) {
 return (&K->ScrollRect.left)[nBar]-K->ScrollInfo[nBar].nPos;
}
// Bitmap-Position nach Client-Position
static int bp2cp(const karte *K, int p, int nBar) {
 return scale(K,p)+shift(K,nBar);
}
// Client-Position nach Bitmap-Position
static int cp2bp(const karte *K, int p, int nBar) {
 return MulDiv(p-shift(K,nBar),K->Scale.N,K->Scale.Z);
}
// Gleitkomma-Version
static float cp2bpf(const karte *K, int p, int nBar) {
 return (float)(p-shift(K,nBar))*K->Scale.N/K->Scale.Z;
}

// Mercator-Koordinate in Pixel-Koordinate (also 2x) umrechnen
static POINT MapCoord(const karte *K,const POINT *pt) {	// Umrechnung
 POINT ret=*pt;
 if (K->kInfo->name) {		// sonst Mercatorprojektion belassen
  PointF pf={(FLOAT)pt->x,(FLOAT)pt->y};
  pf=K->kInfo->kMap(UnmapMercator(pf));
  ret.x=lrint(pf.X);
  ret.y=lrint(pf.Y);
 }
 return ret;
}

#ifdef POLYEDIT
// Pixel-Koordinate in Mercator-Koordinate (also 2x) umrechnen
static POINT UnmapCoord(const karte *K,const POINT *pt) {
 POINT ret=*pt;
 if (K->kInfo->name) {		// sonst Mercatorprojektion belassen
  PointF pf={(FLOAT)pt->x,(FLOAT)pt->y};
  pf=MapMercator(K->kInfo->kUnmap(pf));
  ret.x=lrint(pf.X);
  ret.y=lrint(pf.Y);
 }
 return ret;
}

TCHAR saveFileName[]=T("map3.rc");
POINT saveLast,savePrev;
DWORD saveLastPos,saveCountPos;
int saveCount;

static HANDLE saveOpen() {
 HANDLE ret=CreateFile(saveFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,0,0);
 if (ret==INVALID_HANDLE_VALUE) ret=0;
 if (ret) SetFilePointer(ret,0,NULL,FILE_END);
 return ret;
}

static void savePoint(const karte*K) {
 HANDLE h=saveOpen();
 if (h) {
  HDC dc;
  char s[64];
  DWORD len;
  POINT p={cp2bp(K,K->MousePos.x,SB_HORZ),cp2bp(K,K->MousePos.y,SB_VERT)};
  p=UnmapCoord(K,&p);
  if (!saveCount) {
   saveCountPos=SetFilePointer(h,0,NULL,FILE_CURRENT);
   len=wsprintfA(s," len,0x<country>,%d,%d\t// \n\t",p.x,p.y);
  }else{
   POINTC diff={(char)(p.x-saveLast.x),(char)(p.y-saveLast.y)};
   len=wsprintfA(s,"0x%04X,",*(WORD*)&diff);
  }
  savePrev=saveLast;
  saveLastPos=SetFilePointer(h,0,NULL,FILE_CURRENT);
  saveLast=p;
  if (!(++saveCount&7)) {s[len-1]='\n'; s[len++]=' ';};
  WriteFile(h,s,len,&len,NULL);
  CloseHandle(h);
  p=MapCoord(K,&p);
  p.x=bp2cp(K,p.x,SB_HORZ);
  p.y=bp2cp(K,p.y,SB_VERT);
  dc=GetDC(K->hMap);
  Ellipse(dc,p.x-1,p.y-2,p.x+2,p.y+2);	// Kontrollausgabe
  ReleaseDC(K->hMap,dc);
 }
}

static void saveUndo(const karte*K) {
 if (saveLastPos) {
  HANDLE h=saveOpen();
  if (h) {
   HDC dc;
   POINT p;
   SetFilePointer(h,saveLastPos,NULL,FILE_BEGIN);
   SetEndOfFile(h);
   CloseHandle(h);
   saveLast=savePrev;
   saveCount--;
   saveLastPos=0;	// Kein weiteres Undo!
   p=MapCoord(K,&saveLast);
   p.x=bp2cp(K,p.x,SB_HORZ);
   p.y=bp2cp(K,p.y,SB_VERT);
   dc=GetDC(K->hMap);
   Line(dc,p.x-2,p.y-2,p.x+3,p.y+3);	// durchkreuzen
   Line(dc,p.x-2,p.y+3,p.x+3,p.y-2);
   ReleaseDC(K->hMap,dc);
  }
 }
}

static void saveEnd(const karte*K) {
 HANDLE h=saveOpen();
 if (h) {
  char s[64];
  DWORD len;
  len=wsprintfA(s,"\n\n");
  WriteFile(h,s,len,&len,NULL);
  len=wsprintfA(s,"%4d",saveCount);
  saveCount=0;
  SetFilePointer(h,saveCountPos,NULL,FILE_BEGIN);
  WriteFile(h,s,len,&len,NULL);
  CloseHandle(h);
  MessageBeep(MB_OK);
  saveLastPos=0;		// kein Undo!
 }
}
#endif

// Erzeuge Geokoordinaten-String (mit je 2 Kommastellen) aus Mausposition
static int MkGeoStr(const karte*K, TCHAR*buf, int len) {
 PointF geo;
 TCHAR sx[8],sy[8],rs[32],*px,*py;
 geo.X=cp2bpf(K,K->MousePos.x,SB_HORZ);
 geo.Y=cp2bpf(K,K->MousePos.y,SB_VERT);
 geo=K->kInfo->kUnmap(geo);
 FloatToStr((float)fabs(geo.X),2,sx,elemof(sx));
 FloatToStr((float)fabs(geo.Y),2,sy,elemof(sy));
 LoadString(ghInstance,66,rs,elemof(rs));	// "Geo: %c%s° / %c%s°\0NSOW"
 py=rs+lstrlen(rs)+1;	// auf "N"
 px=py+2+(geo.X<0);	// auf "O" (englisch "E") oder "W"
 py+=geo.Y<0;		// auf "S"
 return wnsprintf(buf,len,rs,*py,sy,*px,sx);
}

/***************
 * GDI-Krempel *
 ***************/
static struct{
 HFONT f[4];	// 0..3: Gradnetz, Städte
 HBRUSH b[11];	// Zuordnung siehe unten
 HPEN p[7];	// Zuordnung siehe unten
}gdi;
static int gdiusage;	// zählt die Fenster mit

static void CreateGdiHandles() {
 CreateSymbGdiHandles();	// --> DlgWetter.c
 if (!gdiusage) {
  static const char sizes[elemof(gdi.f)]={6,8,10,12};
  static const COLORREF brushcolors[elemof(gdi.b)]={
    0x111111,	// grau (TODO: Abschattung Miniaturfenster)
    0x4444FF,	// knallrot (mit Transparenz)
    0xCCCCEE,	// ein Index, wird transparent-rot
    0xCCCCFF,
    0xEECCCC,	// Blau-Index
    0xFFCCCC,
    RGB(192,255,255),	// Meer (blau)
    RGB(128,255,128),	// Land (hellgrün)
    RGB(255,192,128),	// Land (orange)
    RGB(255,255,128),	// Land (dunkelgelb)
    RGB(255,160,192)};	// Land (rosarot)
  LOGBRUSH lbr={BS_SOLID,0x111111};
  int i;
  HDC dc=GetDC(0);
  int lpy=GetDeviceCaps(dc,LOGPIXELSY);	// Schriftgröße wie aus dem Lehrbuch
  ReleaseDC(0,dc);
  for(i=0; i<elemof(gdi.f); i++) {
   // Die Art der Transparenzerzeugung (hier: Gradnetzbeschriftung) beißt sich mit Antialiasing
   gdi.f[i]=CreateFont(-MulDiv(sizes[i],lpy,72),0,0,0,FW_BOLD,0,0,0,0,0,0,
     NONANTIALIASED_QUALITY|PROOF_QUALITY,FF_SWISS|VARIABLE_PITCH,NULL);
  }
  for(i=0; i<elemof(gdi.b); i++) {
   gdi.b[i]=CreateSolidBrush(brushcolors[i]);
  }
  gdi.p[0]=CreatePen(PS_SOLID,1,0x111111);	// normaler grauer Rand
  gdi.p[1]=ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_JOIN_BEVEL,3,&lbr,0,NULL);	// dicker grauer Rand für "gehoverte" Region
  lbr.lbColor=RGB(128,128,255);
  gdi.p[2]=ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_JOIN_BEVEL,3,&lbr,0,NULL);	// dicker blauer Rand für Config.Region
  gdi.p[3]=CreatePen(PS_SOLID,1,0x771111);	// dunkelblau-transparent
  gdi.p[4]=CreatePen(PS_SOLID,0,RGB(255,0,0));	// knallrot, für Miniaturfenster
  lbr.lbColor=RGB(64,64,64);
  gdi.p[5]=ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_JOIN_ROUND,1,&lbr,0,NULL);	// Rand der Landmasse
  gdi.p[6]=CreatePen(PS_DOT,0,lbr.lbColor);	// Innengrenzen
 }
 gdiusage++;
}

static void DeleteGdiHandles() {
 if (!--gdiusage) {
  int i;
  for (i=0; i<sizeof(gdi)/sizeof(HGDIOBJ); i++) DeleteObject(((HGDIOBJ*)&gdi)[i]);
 }
 DeleteSymbGdiHandles();
}

static bool IsBitmapVisible(const karte *K) {
 return K->kInfo && K->kInfo->name && K->kVis&1;
}

// Nur den Weißraum um die Karte herum löschen, um Flackern zu vermeiden
static void EraseOutside(const karte *K, HDC dc) {
 SaveDC(dc);
 if (IsBitmapVisible(K)) ExcludeClipRect(dc,
   bp2cp(K,0,SB_HORZ),
   bp2cp(K,0,SB_VERT),
   bp2cp(K,K->kInfo->szBitmap.cx,SB_HORZ),
   bp2cp(K,K->kInfo->szBitmap.cy,SB_VERT));
 else if (K->kVis&0x08) SelectBrush(dc,gdi.b[6]);	// Meeresfarbe, sonst weiß
 PatBlt(dc,
   K->ScrollRect.left,
   K->ScrollRect.top,
   ScrollRectExt(K,SB_HORZ),
   ScrollRectExt(K,SB_VERT),
   PATCOPY);
 RestoreDC(dc,-1);
}

/*********************************
 * Suchen von Vorhersageregionen *
 *********************************/

static void CalcRects1(const karte *K, MAPMEM *m) {
 int d;
 RECT rc;
 HRGN rgn=CreatePolygonRgn(m->Poly,m->nPoly,ALTERNATE);
 GetRgnBox(rgn,&rc);
 DeleteRgn(rgn);
 m->Area=abs((int)CalcArea(m->Poly,m->nPoly,&m->Centroid));
 InflateRect(&rc,3,3);		// Zugabe für die breiten Linien und spitzen Ecken
 SetRect(&m->rcPoly,
   scale(K,rc.left),
   scale(K,rc.top),
   scale(K,rc.right),
   scale(K,rc.bottom));
 d=(m->CitySize>>1)+(K->kScale>>3)+1;	// Größe logarithmisch der Skalierung (etwas) anpassen
 SETMAX(d,2);			// Minimalen Durchmesser (in Client-Pixel) garantieren (GM_ADVANCED: 3 Pixel)
 SetRectC(&m->rcCity,scale(K,m->CityPos.x),scale(K,m->CityPos.y),d,d);
}
static void CalcRects(const karte *K) {
 int i;
 for (i=0; i<=90; i++) CalcRects1(K,K->map[i]);
}

// Invalidiert ein Rechteck, gegeben in unverschobenen Client-Pixeln
static void InvalRect(const karte *K, const RECT *rc) {
 RECT r;
 CopyRect(&r,rc);
 OffsetRect(&r,shift(K,SB_HORZ),shift(K,SB_VERT));
#ifdef _DEBUG
 {HDC dc=GetDC(K->hMap);
  Rectangle(dc,r.left,r.top,r.right,r.bottom);
  Line(dc,r.left,r.top,r.right-1,r.bottom-1);
  Line(dc,r.left,r.bottom-1,r.right-1,r.top);
  ReleaseDC(K->hMap,dc);
 }
#endif
 InvalidateRect(K->hMap,&r,TRUE);
}

// Invalidiert einen Polygonbereich, bei k==90 den eigenen Standort
static void InvalIndex(const karte *K, int k) {
 if ((unsigned)k<=90) InvalRect(K,&K->map[k]->rcPoly);
}

// Kümmert sich um das (begrenzte) Invalidieren
static void InvalHoverIndex(const karte *K) {
 InvalIndex(K,K->HoverIndex);
}

// Stadt-Name, ggf. mit Nummer
static int GenTextW(const karte *K, int k, PWSTR s, int len) {
 if (K->kVis&0x80) {		// Stadt-Name?
  int sl,rl;			// String-Länge, Ressourcen-Länge
  WCHAR r[64],*rp=r;
  rl=MyLoadStringW(256+k,r,elemof(r));
  sl=lstrlenW(r);
  if (sl<rl) {			// tatsächlich zweiteilig?
   rp+=++sl;			// Zeiger auf die Stadt setzen
   sl=rl-sl;			// Stringlänge korrigieren
  }
  if (K->kVis&0x40) {		// Nummer dazu gewünscht?
   return wnsprintfW(s,len,L"%d:%s",k,rp);	// TEST WIN98!!
  }else{
   StrCpyNW(s,rp,len);
   return sl;
  }
 }else return wnsprintfW(s,len,L"%d",k);	// nur die Nummer
}


// Ermittelt Text-Rechteck je nach Alignment
// Problem: Wo ist TA_BASELINE?
static void GetTextRectW(HDC dc, PCWSTR s, int len, const POINT *org, RECT *rc) {
 int a=GetTextAlign(dc);
 SIZE sz;
 GetTextExtentPoint32W(dc,s,len,&sz);
 if (a&TA_UPDATECP) GetCurrentPositionEx(dc,(POINT*)rc);
 else *(POINT*)rc=*org;
 if (a&TA_RIGHT) rc->left-=a&(TA_CENTER&~TA_RIGHT)?sz.cx>>1:sz.cx;
 rc->right=rc->left+sz.cx;
 if (a&TA_BOTTOM) rc->top-=a&(TA_BASELINE&~TA_BOTTOM)?MulDiv(sz.cy,5,6):sz.cy;
 rc->bottom=rc->top+sz.cy;
}

// bewegliches l,r gegen festes L,R, liefert vorgeschlagene Verschiebung, 0 wenn keine Überlappung
static int intersect(int l, int r, int L, int R) {
 if (l+r<L+R) {	// die Mitten legen das Vorzeichen des Verschiebungsvorschlags fest
  if ((L-=r)<0) return L;	// negative Zahl
 }else{
  if ((R-=l)>0) return R;	// positive Zahl
 }
 return 0;
}
// bewegliches r gegen festes R (um ix,iy aufgeblasen) auf Überlappung prüfen
// Liefert jeweilige vorgeschlagende Fluchtrichtung von r
static U64 intersectR(const RECT*r, const RECT*R, int ix, int iy) {
 U64 ret={0};
 if (!IsRectEmpty(R)
 && (ret.x=intersect(r->left,r->right,R->left-ix,R->right+ix))
 && !(ret.y=intersect(r->top,r->bottom,R->top-iy,R->bottom+iy)))
   ret.x=0;	// X-Überlappung ungültig, wenn's keine Y-Überlappung gibt
 return ret;
}

static U64 intersectRect(const karte *K, const RECT *rc, int kk) {
 int k;
 U64 ret;
 for (k=0; k<=90; k++) {
  const MAPMEM *m=K->map[k];
  if (kk>=0) {	// nur für Text, nicht für Wettersymbole
   ret=intersectR(rc,&m->rcCity,kk!=k?2:0,kk!=k);
   if (ret.ll) return ret;
  }
  if (m->fix&1) {
   ret=intersectR(rc,&m->rcText,1,0);
   if (ret.ll) return ret;
  }
  if (m->fix&2) {
   ret=intersectR(rc,&m->rcSymb,0,0);
   if (ret.ll) return ret;
  }
 }
 return ret;
// if (kk==0) DbgPrintf(("%d überlappt %d\n",kk,k));
}

// bewegliches l,r innerhalb L,R zwingen (dafür Verchiebung ermitteln)
static bool moveinto(int l, int r, int L, int R, int*v) {
 if (r-l>R-L) return false;	// passt überhaupt nicht
 *v=0;
 if ((L-=l)>0) *v=L;		// positiv
 else if ((R-=r)<0) *v=R;	// negativ
 return true;
}
// liefert true wenn's (nach Verschiebung) komplett drin liegt
// hv Bit 0: Erlaube horizontale Verschiebung, Bit 1: dito vertikal
static bool moveRectIntoRect(RECT*r, const RECT*R, BYTE hv) {
 int dx,dy;
 if (!moveinto(r->left,r->right,R->left,R->right,&dx)) return false;
 if (dx && !(hv&1)) return false;
 if (!moveinto(r->top,r->bottom,R->top,R->bottom,&dy)) return false;
 if (dy && !(hv&2)) return false;
 OffsetRect(r,dx,dy);
 return true;
}

static void GenTextRect(const karte *K, HDC dc, int k) {
 if (k>=0) {
/*
  static const BYTE textalign[8]={
    TA_BOTTOM|TA_CENTER,
    TA_BOTTOM|TA_LEFT,
    TA_BASELINE|TA_LEFT,
    TA_TOP|TA_LEFT,
    TA_TOP|TA_CENTER,
    TA_TOP|TA_RIGHT,
    TA_BASELINE|TA_RIGHT,
    TA_BOTTOM|TA_RIGHT};*/
  MAPMEM *m=K->map[k];
  RECT rc;
  RECT vis;
  //int pos,dia;
  POINT pt={(m->rcCity.left+m->rcCity.right)>>1,m->rcCity.bottom};
  U64 fl;	// Fluchtweg
  //POINT ofs;
  WCHAR s[64];
  if (m->fix&1) return;	// schon fixiert!
  //pos=m->TextPos;	// Position der Beschriftung (0..7)
  //dia=m->CitySize;
  //if (dia<4) dia=4;
  CopyRect(&vis,&K->ScrollRect);
  OffsetRect(&vis,-shift(K,SB_HORZ),-shift(K,SB_VERT));
  if (IntersectRect(&rc,&m->rcCity,&vis)) { // Stadt überhaupt sichtbar?
   SetTextAlign(dc,TA_CENTER|TA_TOP);
  //ofs=sincos(dia>>1,pos*450-900);	// Schrift-Ausgangspunkt etwas wegsetzen
  //if (pos==2||pos==6) ofs.y+=4;	// etwa auf vertikale Textmitte setzen (da fehlt TA_VCENTER)
  //pt.x=scale(K,pt.x);
  //pt.y=scale(K,pt.y);
  //pt.x+=ofs.x;
  //pt.y+=ofs.y;
   GetTextRectW(dc,s,GenTextW(K,k,s,elemof(s)),&pt,&rc);
   if (!moveRectIntoRect(&rc,&vis,1)) goto tryupper;
   if ((fl=intersectRect(K,&rc,k)).ll) {
    OffsetRect(&rc,fl.x,0);	// probiere (nur) seitliche Verschiebung
    if (rc.right<pt.x || rc.left>pt.x	// zu viel geschoben
    || intersectRect(K,&rc,k).ll
    || !moveRectIntoRect(&rc,&vis,0)) {
     tryupper:
   // Weitere Anordnungen versuchen
     pt.y=m->rcCity.top;
     SetTextAlign(dc,TA_CENTER|TA_BOTTOM);	// oben darüber
     GetTextRectW(dc,s,GenTextW(K,k,s,elemof(s)),&pt,&rc);	// Quatsch, da muss es was besseres geben!
     if (!moveRectIntoRect(&rc,&vis,1)) goto leer;
     if ((fl=intersectRect(K,&rc,k)).ll) {
      OffsetRect(&rc,fl.x,0);	// probiere (nur) seitliche Verschiebung
      if (rc.right<pt.x || rc.left>pt.x	// zu viel geschoben
      || intersectRect(K,&rc,k).ll
      || !moveRectIntoRect(&rc,&vis,0)) goto leer;
     }
    }
   }
  }else leer: SetRectEmpty(&rc);
  if (!EqualRect(&m->rcText,&rc)) {
   InvalRect(K,&m->rcText);
   InvalRect(K,&rc);
   CopyRect(&m->rcText,&rc);
  }
  m->fix|=1;			// fixieren
 }
}

// 1. Parameter: Tag (Bits 5:4)
// 2. Parameter: Region (0..89)
static U64 GetTagWetter(BYTE kWetter, int k) {
 U64 wetter[4];
 GetForecast(k,wetter);		// gehe zu DlgWetter und greife in den Cache24
 return wetter[kWetter>>4&3];	// wähle Tag 0..3
}

static void GenWetterRect(const karte*K, HDC dc, int k, int kinval) {
 if ((unsigned)k<90) {
  RECT alt,neu;
  POINT mi;
  MAPMEM *m=K->map[k];
  bool repaint=false;
  if (m->fix&2) return;		// war schon mal dran: nichts tun!
  CopyRect(&alt,&m->rcSymb);
  CopyRect(&neu,&alt);
  mi.x=scale(K,m->Centroid.x);
  mi.y=scale(K,m->Centroid.y);
  if (kinval<0 || kinval==k) {
   U64 v=GetTagWetter(K->kWetter,k);	// Wetter des gewünschten Tages
   Measure(dc,v,(BYTE)(K->kWetter&0x0F),&neu);
   if (!IsRectEmpty(&neu)) OffsetRect(&neu,mi.x,mi.y);
   repaint=true;
  }
  if (!EqualRect(&alt,&neu)) {
   InvalRect(K,&alt);
   CopyRect(&m->rcSymb,&neu);
   repaint=true;
  }
  if (repaint) InvalRect(K,&neu);
  m->fix|=2;
 }
}

static void WetterDC(const karte*K, HDC dc) {
 static const char scales[4]={24,32,48,64};
 int sc=scales[K->kAutoScale>>6];	// Größe festlegen
 SetMapMode(dc,MM_ANISOTROPIC);
 SetWindowExtEx(dc,64,64,NULL);
 SetViewportExtEx(dc,sc,sc,NULL);
}

// Verteilt Wettersymbole und Städte-Namen auf Nicht-Überlappung
// newWetter <0: Alle Wettersymbole
// newWetter = 0..89: Ein Wettersymbol
// newWetter >= 90: Kein Wettersymbol ist neu (könnte aber neu platziert werden)
static void InvalSymbChange(const karte *K, int newWetter) {
 int k,i;
 HDC dc=GetDC(K->hMap);		// hier wird nichts gezeichnet!
 WetterDC(K,dc);
 for (i=0; i<=90; i++) K->map[i]->fix=0;	// Fixierungs-Bits löschen
 GenWetterRect(K,dc,newWetter,newWetter);	// absolut bevorzugt
 GenWetterRect(K,dc,K->HoverIndex,newWetter);
 GenWetterRect(K,dc,Config.Region,newWetter);
 for (i=0; i<90; i++) GenWetterRect(K,dc,i,newWetter);
 SetMapMode(dc,MM_TEXT);
 SelectFont(dc,gdi.f[K->kAutoScale>>4&3]);
 GenTextRect(K,dc,K->HoverIndex);
 GenTextRect(K,dc,Config.Region);
 if (Config.uRegion>=0) GenTextRect(K,dc,90);
 for (i=0x0F; i>=0; i--) {	// von großen zu kleinen Nestern
  for (k=0; k<90; k++) {	// jeweils durchgehen
   if (K->map[k]->CitySize==i) GenTextRect(K,dc,k);
  }
 }
 ReleaseDC(K->hMap,dc);
//#ifdef _DEBUG
// for (i=0; i<=90; i++) dprintf("%d",!IsRectEmpty(&K->map[i]->rcText));
// dprintf("\n");
//#endif
}

/********************
 * Variablen setzen *
 ********************/

// Ermittelt das Fenster-Handle der Wetter-Eigenschaftsseite,
// Null wenn nicht im Vordergrund; für's Hot-Tracking der Wettervorhersage
static HWND GetPropWetter() {
 if (PropWnd) {
  HWND PageWnd=PropSheet_GetCurrentPageHwnd(PropWnd);
  if (PageWnd && PropSheet_HwndToIndex(PropWnd,PageWnd)==7) return PageWnd;
 }	// Der Index 7 stimmt hier immer
 return 0;
}

static void SetShowRegion(char value) {
 HWND hWetter=GetPropWetter();
 if (hWetter) SetCurRegion(hWetter,value);
}

static void SetRegion(char r) {
 if (r<0) return;
 InfoPropSheet(18,Config.Region);
 Config.Region=r;
 SetShowRegion(r);
}

static void PropShowWetter(void) {
 ShowProperties();
 PropSheet_SetCurSel(PropWnd,0,7);
}

// Sucht euklidisch nächstgelegene Vorhersage-Stadt,
// bezogen auf die aktuell verwendete Projektion
// Liefert immer 0 <= Wert < 90
static int FindNearestCity(const karte *K, int x, int y) {
 int i=0;
 int r=-1;			// sollte nicht dabei bleiben
 DWORD d2min=(DWORD)-1;
 for (i=0; i<90; i++) {
  MAPMEM *m=K->map[i];
  if (m->nPoly) {
   int dx=x-m->CityPos.x;
   int dy=y-m->CityPos.y;
   DWORD d2=dx*dx+dy*dy;	// Abstandsquadrat; aufs Wurzelziehen wird verzichtet
   if (d2min>d2) d2min=d2, r=i;	// Minimum merken
  }
 }
 return r;
}

static void OffsetPoints(POINT*pt, int len, int dx, int dy) {
 int i;
 for (i=0; i<len; i++) {
  pt[i].x+=dx;
  pt[i].y+=dy;
 }
}

static int SearchStaat(const karte *K, int x, int y) {
 int i;
 for (i=0; i<NUMSTAAT; i++) {
  HRGN rgn=K->staat[i];
  if (rgn && PtInRegion(rgn,x,y)) return i;
 }
 return -1;
}

/*******************
 * Zeichenroutinen *
 *******************/

// Berechnet eigenen Standort in K->map[90] und generiert ein "X" als Zwölfeck
static void CalcMyPos(karte *K, bool full) {
 int i;
 MAPMEM *m=K->map[90];
 POINT pt={Config.uPos.x,Config.uPos.y};
 m->CityPos=MapCoord(K,&pt);
 K->MyPosStaat=SearchStaat(K,m->CityPos.x,m->CityPos.y);
 if (full) {
  m->nPoly=12;
  for (i=0; i<12; i++) m->Poly[i]=sincos(i%3?10:4,i*300);	// ein "X" erzeugen
  OffsetPoints(m->Poly,12,m->CityPos.x,m->CityPos.y);
  for (i=0; i<12; i++) InflateRectForPoint(&K->MapExtent,&m->Poly[i]);
 }
 //m->TextPos=4;	// unten
}

// Map-Mode: MM_TEXT mit ViewportOrg
__forceinline void DrawCityName(karte *K, HDC dc, int k) {
 MAPMEM *m=K->map[k];
 WCHAR s[64];
 if (IsRectEmpty(&m->rcText)) return;
#if 0//def _DEBUG
 SaveDC(dc);
 SelectPen(dc,GetStockPen(BLACK_PEN));
 SelectBrush(dc,GetStockBrush(HOLLOW_BRUSH));
 SetGraphicsMode(dc,GM_COMPATIBLE);
 {RECT *r=&m->rcText;
  Rectangle(dc,r->left,r->top,r->right,r->bottom);
 }
 RestoreDC(dc,-1);
#endif
 TextOutW(dc,m->rcText.left,m->rcText.top,s,GenTextW(K,k,s,elemof(s)));
}

static void DrawCityNames(karte *K, HDC dc) {
 int i,k;
 SetTextAlign(dc,TA_TOP|TA_LEFT);
 k=90+(Config.uRegion>=0);
 for (i=0; i<k; i++) DrawCityName(K,dc,i);
}
 
static bool ManipulateDIBits(HDC hdc, HBITMAP hBmp, void(*fn)(COLORREF*,int)) {
// Bitmap aus DC extrahieren
 BITMAP bm;
 BITMAPINFOHEADER bi;
 COLORREF *bits;
 int k;
 bool ret=false;
 GetObject(hBmp,sizeof(bm),&bm);
 InitStruct(&bi,sizeof(bi));
 bi.biWidth = bm.bmWidth;
 bi.biHeight = bm.bmHeight;
 bi.biPlanes = bm.bmPlanes;		// 1
 bi.biBitCount = bm.bmBitsPixel;	// 32
 k=bm.bmWidth*bm.bmHeight;		// Größe in DWORDs
 bits=LocalAlloc(LMEM_FIXED,k*sizeof(COLORREF));
 if (bits) {
  if (GetDIBits(hdc,hBmp,0,bm.bmHeight,(void**)bits,(BITMAPINFO*)&bi,DIB_RGB_COLORS)) {
// Pixel bearbeiten
   fn(bits,k);
// Bitmap zurücktransferieren
   if (SetDIBits(hdc,hBmp,0,bm.bmHeight,(void**)bits,(BITMAPINFO*)&bi,DIB_RGB_COLORS)) ret=true;
  }
  LocalFree(bits);
 }
 return ret;
}

static void AllTransparent(COLORREF *cr, int k) {
 __stosd(cr,0xFF000000,k);
}

/* PROBLEM: Windows-Stifte und -Pinsel haben immer T=0 (oberstes Byte),
 * da lässt sich Windows offenbar nicht austricksen.
 * Deshalb muss hier nachträglich - je nach Farbe - der Transparenzwert angepasst werden
 * Nicht überzeichnete Pixel behalten den bei TransAll() gesetzten Pixelwert.
 * Achtung! Es sind BGR-Werte!
 */
static void ReplaceColors(COLORREF *cr, int k) {
 int i;
 for (i=0; i<k; i++, cr++) {
  switch (*cr) {
// Polygonfarben:
   case 0xCCCCEE: *cr=RGBA(255,192,192,64); break;	// rot, glasig
   case 0xCCCCFF: *cr=RGBA(255,192,192,128); break;	// dito, weniger transparent
   case 0xEECCCC: *cr=RGBA(192,192,255,64); break;	// blau, glasig
   case 0xFFCCCC: *cr=RGBA(192,192,255,128); break;
   case 0x111111: *cr=RGBA(0,0,0,128); break;		// schwarzer Rand (erscheint grau)
// Gradnetz-Farbe:
   case 0x111177: *cr=RGBA(128,0,0,160); break;		// dunkelblau, transparent
   case 0x771111: *cr=RGBA(0,0,128,160); break;
// Stadtklecks-Farbe:
   case 0x4444FF: *cr=RGBA(255,0,0,192); break;		// knallrot, etwas transparent
   case 0xFF4444: *cr=RGBA(0,0,255,192); break;
// Landmasse-Polygonfarben:
   case 0x80FF80: *cr=RGBA(0,255,0,64); break;
   case 0xFFC080: *cr=RGBA(0,128,255,64); break;
   case 0xFFFF80: *cr=RGBA(0,255,255,64); break;
   case 0xFFA0C0: *cr=RGBA(128,0,255,64); break;
// Farbe behalten; nicht benutzte Pixel werden hier 100% transparent
   default: *cr^=0xFF000000;
  }
 }
}

// Setzt Offset und Skalierung. Beim Offset geht die Scroll-Position bereits mit ein.
// dx und dy sind
// - der Aktualisierungsausschnitt beim Speichergerätekontext
// die linke obere Ecke
static void PrepareDC(const karte *K, HDC dc, int dx, int dy) {
 SetGraphicsMode(dc,GM_ADVANCED);
 SetBkMode(dc,TRANSPARENT);
 SetViewportOrgEx(dc,shift(K,SB_HORZ)-dx,shift(K,SB_VERT)-dy,NULL);
}
static void ScaleDC(const karte*K, HDC dc) {
 SetMapMode(dc,MM_ANISOTROPIC);
 SetWindowExtEx(dc,K->Scale.N,K->Scale.N,NULL);
 SetViewportExtEx(dc,K->Scale.Z,K->Scale.Z,NULL);
}
#define UnscaleDC(dc) SetMapMode((dc),MM_TEXT);

// dc muss MM_TEXT haben
// DoDraw wird mit unskaliertem, aber auf Bitmap-Ausschnitt verschobenen dc aufgerufen
static bool DrawTransparent(karte *K, HDC dc, RECT *rc, void(*DoDraw)(karte*,HDC)) {
 bool ret=false;
#ifndef UNICODE
 if (!NoAlpha)
#endif
 {
  SIZE sz={rc->right-rc->left,rc->bottom-rc->top};
  HDC memdc=CreateCompatibleDC(dc);
  HBITMAP membm=CreateCompatibleBitmap(dc,sz.cx,sz.cy);
  if (membm) {
   HBITMAP obm=SelectBitmap(memdc,membm);
   const BLENDFUNCTION bf={AC_SRC_OVER,0,255,AC_SRC_ALPHA};
   if (ManipulateDIBits(memdc,membm,AllTransparent)) {
    POINT org;
    GetViewportOrgEx(dc,&org);
    SaveDC(memdc);
    PrepareDC(K,memdc,rc->left,rc->top);
    DoDraw(K,memdc);
    RestoreDC(memdc,-1);	// AlphaBlend() geht nur mit MM_TEXT sowie <memdc> ohne Offset
    if (ManipulateDIBits(memdc,membm,ReplaceColors)
    && AlphaBlend(dc,rc->left-org.x,rc->top-org.y,sz.cx,sz.cy,memdc,0,0,sz.cx,sz.cy,bf)) ret=true;
   }	// AlphaBlend() funktioniert nicht unter Win98 in Nieschütz! Liefert 0x1D7.
	// Funktioniert unter Win98 in Chemnitz, aber liefert 0x2F7. Pixelzeilenzahl?
   SelectBitmap(memdc,obm);
   DeleteBitmap(membm);
  }
  DeleteDC(memdc);
 }
 if (!ret) {	// Wenn etwas schief ging … (Tobias' Win98-Rechner)
  DoDraw(K,dc);
 }
 return ret;
}

static void DrawBorderLine(karte*K, HDC dc, const BORDEREX*bo, HPEN pen) {
 SelectPen(dc,pen);
 Polyline(dc,bo->points,bo->n);
#ifdef POLYEDIT
 {HPEN open=SelectPen(dc,GetStockPen(BLACK_PEN));
  int i;
  for (i=0; i<bo->n; i++)
    Ellipse(dc,bo->points[i].x,bo->points[i].y,bo->points[i].x+1,bo->points[i].y+1);
  SelectPen(dc,open);
 }
#endif
}

// Landmasse und Grenzen malen
static void DoDrawGrenzen(karte*K, HDC dc) {
 int i;
 const BORDEREX*bo;
 ScaleDC(K,dc);
 if (K->kVis&0x08)	// Landmasse?
   for (i=0; i<NUMSTAAT; i++)
     FillRgn(dc,K->staat[i],gdi.b[7+Vierfarben[i]]);
 if (K->kVis&0x04)	// Landgrenzen?
   for (bo=&K->bo; bo->valid; bo=IterateBorderex(bo))
     if ((bo->staaten[0]|bo->staaten[1])>=0
     && bo->staaten[1]!=0x7F)		// RUS+Kunstnordgrenze
       DrawBorderLine(K,dc,bo,gdi.p[6]);
 if (K->kVis&0x02)	// Küstenlinie?
   for (bo=&K->bo; bo->valid; bo=IterateBorderex(bo))
     if ((bo->staaten[0]|bo->staaten[1])<0)
       DrawBorderLine(K,dc,bo,gdi.p[5]);
 UnscaleDC(dc);
}

// Gradnetz und Gradzahlen in Einheitsfarbe ausgeben
static void DoDrawGradnetz(karte*K, HDC dc) {
 ScaleDC(K,dc);
 SelectFont(dc,gdi.f[K->kAutoScale>>4&3]);
 SelectPen(dc,gdi.p[3]);
 SelectBrush(dc,GetStockBrush(HOLLOW_BRUSH));
 SetTextColor(dc,0x771111);		// Blau-Index
 SetTextAlign(dc,TA_TOP|TA_LEFT);
 K->kInfo->kGradnetz(K->kInfo,dc);
 UnscaleDC(dc);
}

// Polygon malen
static void DoDrawPoly(karte*K, HDC dc, int k) {
 int i=(k==90?Config.uRegion:k)<60?4:2;
 if (k==K->HoverIndex) i++;
 SelectBrush(dc,gdi.b[i]);
 i&=1;
 if (!i && k==Config.Region) i=2;
 SelectPen(dc,gdi.p[i]);
 Polygon(dc,K->map[k]->Poly,K->map[k]->nPoly);	// zeichnet nichts für nPoly==0
}
// Polygon mit Vorbereitung malen
static void DoDrawPolyP(karte*K, HDC dc, int k) {
 ScaleDC(K,dc);
 DoDrawPoly(K,dc,k);
 UnscaleDC(dc);
}

// Stadtklecks malen
static void DoDrawCity(const karte *K, HDC dc, int k) {
 MAPMEM *m=K->map[k];
 if (m->nPoly && m->CitySize) {
  Ellipse(dc,m->rcCity.left,m->rcCity.top,m->rcCity.right,m->rcCity.bottom);
 }
}
static void PrepareDrawCity(HDC dc) {
 SelectBrush(dc,gdi.b[1]);
 SelectPen(dc,GetStockPen(NULL_PEN));
}
// Stadtklecks malen, mit Vorbereitung von Pinsel und Stift
static void DoDrawCityP(const karte *K, HDC dc, int k) {
 PrepareDrawCity(dc);
 DoDrawCity(K,dc,k);
}

static void DoDrawPolys(karte*K, HDC dc) {
 int k;
 ScaleDC(K,dc);
 for (k=0; k<=90; k++) {
  if (k!=K->HoverIndex && k!=Config.Region) DoDrawPoly(K,dc,k);
 }
 UnscaleDC(dc);
 if (K->kVis&0xC0) {
  PrepareDrawCity(dc);
  for (k=0; k<=90; k++) {
   if (k!=K->HoverIndex && k!=Config.Region) DoDrawCity(K,dc,k);
  }
 }
 k=Config.Region;
 if (k>=0 && k!=K->HoverIndex) {
  DoDrawPolyP(K,dc,k);		// mit blauem Rand (SELECTED)
  if (K->kVis&0xC0) DoDrawCityP(K,dc,k);
 }
 k=K->HoverIndex;
 if (k>=0) {
  DoDrawPolyP(K,dc,k);		// mit weißem Rand zuletzt (HOVERED)
  if (K->kVis&0xC0) DoDrawCityP(K,dc,k);
 }
}

static void BltBitmap(HDC dc, HBITMAP hbm) {
 HDC sdc;	// Quell-DC
 HBITMAP obm;	// Original-Bitmap (1x1)
 BITMAP bm;	// um die Größe von hbm zu erfahren
 GetObject(hbm,sizeof(bm),&bm);
 sdc=CreateCompatibleDC(dc);
 obm=SelectBitmap(sdc,hbm);
 BitBlt(dc,0,0,bm.bmWidth,bm.bmHeight,sdc,0,0,SRCCOPY);
 SelectBitmap(sdc,obm);
 DeleteDC(sdc);
}

// Malt die Bitmap in den geeignet vorbereiteten Gerätekontext
static void PaintPicture(const karte *K, HDC dc) {
 if (K->pPicture && K->kInfo->name) {
  if (K->bPictureHBM) {	// Gdiplus (ab XP oder bei vorhandener gdiplus.dll)
   SetStretchBltMode(dc,HALFTONE);
   SetBrushOrgEx(dc,-K->ScrollInfo[SB_HORZ].nPos,-K->ScrollInfo[SB_VERT].nPos,NULL);
   BltBitmap(dc,K->hPicture);
  }else{	// OLE
   SIZE szHiMetric;
   RECT rc;
   (*(IPictureVtbl**)K->pPicture)->get_Width(K->pPicture,&szHiMetric.cx);
   (*(IPictureVtbl**)K->pPicture)->get_Height(K->pPicture,&szHiMetric.cy);
   SetRect(&rc,0,0,K->kInfo->szBitmap.cx,K->kInfo->szBitmap.cy);
   (*(IPictureVtbl**)K->pPicture)->Render(K->pPicture,dc,
     rc.left,rc.top,rc.right,rc.bottom,
     0,szHiMetric.cy,szHiMetric.cx,-szHiMetric.cy,&rc);
  }
 }
}

static void DrawAll(karte *K, HDC dc, const RECT *rcInval) {
 RECT rc;
 int k;
 CopyRect(&rc,&K->ScrollRect);
 if (rcInval) CopyRect(&rc,rcInval);

 PrepareDC(K,dc,0,0);
 if (IsBitmapVisible(K)) {
  ScaleDC(K,dc);
  PaintPicture(K,dc);
  UnscaleDC(dc);
 }
 
 if (K->kVis&0x0E) {
  if (IsBitmapVisible(K)) DrawTransparent(K,dc,&rc,DoDrawGrenzen);	// noch eine transparente Lage
  else DoDrawGrenzen(K,dc);					// ohne Bitmap: deckend
 }
 if (K->kVis&0x10) DrawTransparent(K,dc,&rc,DoDrawGradnetz);	// 1. Lage
 if (K->kVis&0x20) DrawTransparent(K,dc,&rc,DoDrawPolys);	// 2. Lage, ggf. mit Stadt-Klecksen

// Alles weitere ist wieder deckend
 if (K->kVis&0xC0) {	// Städte, ohne Stadt-Kleckse, wenn bereits mit 2. Lage gezeichnet
  int k;
  PrepareDrawCity(dc);	// hier: ohne Einhaltung einer Reihenfolge
  for (k=0; k<=90; k++) DoDrawCity(K,dc,k);
 }
 if (K->kVis&0xC0) {
  SelectFont(dc,gdi.f[K->kAutoScale>>4&3]);
  DrawCityNames(K,dc);
 }

 if (K->kWetter&15) {
  WetterDC(K,dc);
  for (k=0; k<90; k++) {
   U64 v=GetTagWetter(K->kWetter,k);
   if (v.ull) {
    POINT pt=K->map[k]->Centroid;
    SetViewportOrgEx(dc,bp2cp(K,pt.x,SB_HORZ),bp2cp(K,pt.y,SB_VERT),NULL);
    switch (K->kWetter&15) {
     case 1: ptSymbolTag(dc,v); break;
     case 2: if (v.Tag) ptTempTag(dc,v); break;
     case 3: ptSymbolNacht(dc,v,K->kWetter>>4&3); break;	// Tag 0..3 (wegen Mondphase)
     case 4: if (v.Nacht) ptTempNacht(dc,v); break;
     case 5: if (v.Tag) ptNSW(dc,v); break;	// Niederschlagswahrscheinlichkeit
     case 6: if (v.Nacht) ptWind(dc,v); break;
     case 7: ptWarnung(dc,v); break;
    }
   }
  }
 }
#ifdef _DEBUG	// Kontrollrechtecke ausgeben
 UnscaleDC(dc);
 SetWindowOrgEx(dc,0,0,NULL);
 SetViewportOrgEx(dc,shift(K,SB_HORZ),shift(K,SB_VERT),NULL);
 SetGraphicsMode(dc,GM_COMPATIBLE);
 SelectPen(dc,GetStockPen(BLACK_PEN));
 SelectBrush(dc,GetStockBrush(HOLLOW_BRUSH));
 for (k=0; k<90; k++) {
  RECT *r=&K->map[k]->rcText;
  Rectangle(dc,r->left,r->top,r->right,r->bottom);
  r=&K->map[k]->rcSymb;
  Rectangle(dc,r->left,r->top,r->right,r->bottom);
 }
#endif
}

/************************************************
 * StatusXxx-Funktionen, je eine für jedes Feld	*
 ************************************************/
// Statuszeile Feld 0: Skalierung
static void StatusScale(const karte *K) {
 TCHAR s[32],t[32];
 LoadString(ghInstance,65,t,elemof(t));
 wnsprintf(s,elemof(s),t,MulDiv(100,K->Scale.Z,K->Scale.N));
 SendMessage(K->hStatus,SB_SETTEXT,0,(LPARAM)s);
}
// Statuszeile Feld 1: Geoposition der Maus
static void StatusGeo(const karte *K) {
 TCHAR s[64];
 MkGeoStr(K,s,elemof(s));
 SendMessage(K->hStatus,SB_SETTEXT,1,(LPARAM)s);
}
// Statuszeile Feld 2: Staat mit Flagge
static void StatusStaat(const karte *K) {
 SendMessage(K->hStatus,SB_SETTEXT,2|SBT_OWNERDRAW,0);
}
#ifdef POLYEDIT
static void StatusMercator(const karte*K) {
 POINT p;
 TCHAR s[64];
 p.x=cp2bp(K,K->MousePos.x,SB_HORZ);
 p.y=cp2bp(K,K->MousePos.y,SB_VERT);
 p=UnmapCoord(K,&p);
 wnsprintf(s,elemof(s),T("%d/%d"),p.x,p.y);
 SendMessage(K->hStatus,SB_SETTEXT,2,(LPARAM)s);
}
#endif
// Statuszeile Feld 3: Vorhersageregion mit Nummer
static void StatusRegion(const karte *K) {
 TCHAR s[64];
 s[0]=0;	// Löschen wenn in keiner Region
 if (K->HoverIndex>=0) {
  TCHAR r[64],t[32];
  LoadString(ghInstance,68,t,elemof(t));
  LoadString(ghInstance,256+K->HoverIndex,r,elemof(r));
  wnsprintf(s,elemof(s),t,K->HoverIndex,r);
 }
 SendMessage(K->hStatus,SB_SETTEXT,3,(LPARAM)s);
}
// Statuszeile Feld 4: Welches Wetter von welchem Tag (entsprechend Menüpunkt)
// ODER Schwerwetter-Anzeige
static void StatusWetter(karte *K) {
 TCHAR s[128];
 s[0]=0;	// Löschen wenn keine Wetteranzeige
 if (K->kWetter&7) {
  if ((BYTE)K->HoverIndex<90) {
   TCHAR st[64],sn[64];
   BYTE b=0;	// bool-Bits: Bit 0 = Tag-Warnung, Bit 1 = Nacht-Warnung
   U64 v=GetTagWetter(K->kWetter,K->HoverIndex);
   if ((BYTE)0xE6>>(K->kWetter&7)&1 && SchwerwetterText(st,elemof(st),false,v,true)) b|=1;
   if ((BYTE)0xF8>>(K->kWetter&7)&1 && SchwerwetterText(sn,elemof(sn),true,v,true)) b|=2;
   if (b) {
    switch (b) {
     case 1: lstrcpyn(K->strStatus4,st,elemof(K->strStatus4)); break;	// nur Tag
     case 2: lstrcpyn(K->strStatus4,sn,elemof(K->strStatus4)); break;	// nur Nacht
     case 3: wnsprintf(K->strStatus4,elemof(K->strStatus4),T("%s / %s"),st,sn); break;	// Tag und Nacht
    }
    SendMessage(K->hStatus,SB_SETTEXT,4|SBT_OWNERDRAW,0);
    return;
   }
  }
  {HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
   TCHAR s1[32],s2[64],t[32];
   GetMenuString(m,0x60+(K->kWetter&15),s2,elemof(s2),0);
   DestroyMenu(m);
   StripAmpersand(s2);
   TagesName((K->kWetter>>4)&3,s1,elemof(s1),0);
   LoadString(ghInstance,69,t,elemof(t));
   wnsprintf(s,elemof(s),t,s1,s2);
  }
 }
 SendMessage(K->hStatus,SB_SETTEXT,4,(LPARAM)s);
}

/***************
 * Treffertest *
 ***************/

static void SetHoverStaat(karte *K, char value) {
 if (K->HoverStaat==value) return;
 K->HoverStaat=value;
 if (K->kShow&0x40) StatusStaat(K);
}

static int SearchHoverStaat(const karte *K) {
 return SearchStaat(K,
   cp2bp(K,K->MousePos.x,SB_HORZ),
   cp2bp(K,K->MousePos.y,SB_VERT));
}

static void SetHoverIndex(karte *K, char value) {
 if (K->HoverIndex==value) return;
 if (K->kVis&0x20) InvalHoverIndex(K);
 K->HoverIndex=value;
 if (K->kVis&0x20) InvalHoverIndex(K);
 InvalSymbChange(K,90);
 if (K->kShow&0x40) {
  StatusRegion(K);
  StatusWetter(K);	// möglicherweise Schwerwetteranzeige
 }
 if (K->kWetter&0x80) SetShowRegion(value);
}

// Sucht, welche Vorhersageregion zur angegebenen Kartenpixel-Koordinate passt
static int SearchIndex(const karte *K, int x, int y) {
 int i;
 for (i=91; --i>=0; ) {
  MAPMEM *m=K->map[i];
  HRGN rgn=CreatePolygonRgn(m->Poly,m->nPoly,ALTERNATE);
  if (PtInRegion(rgn,x,y)) {
   DeleteObject(rgn);
   return i;
  }
  DeleteObject(rgn);
 }
 return -1;
}

// Sucht, welche Vorhersageregion zur (gerade geänderten) Mausposition passt
static int SearchHoverIndex(const karte *K) {
 return SearchIndex(K,
   cp2bp(K,K->MousePos.x,SB_HORZ),
   cp2bp(K,K->MousePos.y,SB_VERT));
}

/**************
 * Rollbalken *
 **************/
// Kopiert das Verhalten von SetScrollInfo(): verändert nPage und nPos: Nicht ganz!
// Sondern erlaubt nPage > nMax-nMin+1 (weiße Fensterflächen);
// für diesen Fall wird nPos derart begrenzt, dass die Bitmap beliebig im Fenster
// platziert wird, aber nicht herausfällt.
// In diesem Fall begrenzt SetScrollInfo(), und GetScrollPos() darf nicht benutzt werden.
// Scrollbalken verschwinden sowieso, so dass keine SB_THUMBTRACK-Nachricht kommt.
// Voraussetzung: nMin<=nMax!
// Liefert neues si->nPos
static int LimitScroll(SCROLLINFO *si) {
 if (si->nMin<=si->nMax) {
  int a=si->nMin;		// Anfang
  int e=si->nMax-si->nPage+1;	// Ende (bzw. Anfang für d<si->nPage)
  int p=si->nPos;
  if (a>e) {
   a=e;
   e=si->nMin;			// tauschen
  }
  SETMAX(p,a);
  SETMIN(p,e);
  if (si->nPos!=p) DbgPrintf(("LimitScroll: alt=%d, neu=%d\n",si->nPos,p));
  si->nPos=p;
 }
 return si->nPos;
}

// Schiebe Scrollbalken um oder auf bestimmte Position
// Liefert tatsächliche Verschiebedifferenz, für ScrollWindow()
static int GenShift(karte *K, int nBar, char kode, int dx) {
 SCROLLINFO *si=K->ScrollInfo+nBar;
 int d=si->nPos;
 switch (kode) {
  case SB_LINELEFT:
  case SB_LINERIGHT: {
   dx=8;
   if (GetKeyState(VK_SHIFT)<0) dx<<=2;
   if (kode==SB_LINELEFT) dx=-dx;
  }break;
  case SB_PAGELEFT: dx=si->nPage; break;
  case SB_PAGERIGHT: dx=-(int)si->nPage; break;
  case SB_THUMBTRACK:
  case SB_THUMBPOSITION: {	// kann nur vorkommen, wenn Rollbalken sichtbar
   si->fMask=SIF_TRACKPOS;	// Position (als 32-Bit-Wert) holen
   GetScrollInfo(K->hMap,nBar,si);
   si->nPos=si->nTrackPos;	// dx sollte hier 0 sein!
  }break;
  case SB_LEFT: si->nPos=si->nMin; break;
  case SB_RIGHT: si->nPos=si->nMax; break;
 }
 si->nPos+=dx;
 LimitScroll(si);
 if (K->kShow&4<<nBar) {
  si->fMask=SIF_POS;	// reicht
  SetScrollInfo(K->hMap,nBar,si,TRUE);
 }
 if (si->nPos!=d) DbgPrintf(("GenShift(%d,%d,%d): alt=%d, neu=%d\n",nBar,kode,dx,d,si->nPos));
 return d-si->nPos;
}

// entweder wenn sich die Mausposition ändert
// oder sich die Karte unter der Maus verschiebt
static void MousePosChanged(karte*K) {
#ifdef POLYEDIT
 if (K->kShow&0x40) StatusMercator(K);
#else
 SetHoverStaat(K,(char)SearchHoverStaat(K));
#endif
 SetHoverIndex(K,(char)SearchHoverIndex(K));	// setzt ggf. Statuszeile
 if (K->kShow&0x40) StatusGeo(K);
}

// Fensterinhalt um <dx> und <dy> verschieben
// liefert <true> wenn tatsächlich verschoben wurde
static bool Scroll(karte *K, int dx, int dy) {
 ScrollWindow(K->hMap,dx,dy,&K->ScrollRect,NULL);
 if (dx||dy) {
  InvalSymbChange(K,90);
  if (K->hMini) InvalidateRect(K->hMini,NULL,FALSE);
  if (!K->bFromMouse) MousePosChanged(K);
  return true;
 }
 return false;
}

static bool HandleHScroll(karte *K, char kode) {
 return Scroll(K,GenShift(K,SB_HORZ,kode,0),0);
}

static bool HandleVScroll(karte *K, char kode) {
 return Scroll(K,0,GenShift(K,SB_VERT,kode,0));
}

// Schieben mit Tastatur oder Maus
// Liefert <true> wenn tatsächlich geschoben wurde
static bool HandleShift(karte *K, int dx, int dy) {
 return Scroll(K,GenShift(K,SB_HORZ,-1,-dx),GenShift(K,SB_VERT,-1,-dy));
}

#pragma optimize("g",off)

// Setzt Scrollbereich, Scrollpage
// Aufzurufen bei WM_SIZE und wenn sich Skalierung ändert
// MousePos.x/y: Client-Position, die bei Größenänderung konstant gehalten soll
// Liefert Verschiebewert
static int AdjustScroll(karte *K, int nBar) {
 SCROLLINFO *si=K->ScrollInfo+nBar;	// passende Info
 int x=si->nPos;			// Vorherige Position
 unsigned b=si->nMax-si->nMin+1;	// Vorhergehende Ausdehnung
 si->nMin=scale(K,(&K->MapExtent.left)[nBar]);		// SB_VERT -> .top
 si->nMax=scale(K,(&K->MapExtent.right)[nBar])-1;	// SB_VERT -> .bottom
 si->nPage=ScrollRectExt(K,nBar);	// Neue Seitengröße
 if (b>1) {
  int cc;				// Zentrierposition
  if (K->bFromMouse) cc=(&K->MousePos.x)[nBar]-(&K->ScrollRect.left)[nBar];	// Mausposition
  else cc=si->nPage>>1;			// Mitte Client
  si->nPos=MulDiv(x+cc,si->nMax-si->nMin+1,b)-cc;	// Kartenpixel unter der Maus beibehalten
 }
 LimitScroll(si);
 if (K->kShow&0x04<<nBar) {
  si->fMask=SIF_ALL;
  SetScrollInfo(K->hMap,nBar,si,TRUE);
 }
 if (x!=si->nPos) DbgPrintf(("AdjustScroll(%d): alt=%d, neu=%d\n",nBar,x,si->nPos));
 return x-si->nPos;
}

// Setzt Scrollbereich, Scrollpage, ggf. wird ScrollWindow() aufgerufen
// Aufzurufen bei WM_SIZE und wenn sich Skalierung ändert
static bool AdjustScrollRange(karte *K) {
 return Scroll(K,AdjustScroll(K,SB_HORZ),AdjustScroll(K,SB_VERT));
}
#pragma optimize("g",on)

/*****************************************
 * Gleitkomma, Koordinatentransformation *
 *****************************************/
int FloatToStr(float v, int nk, PTSTR s, int len) {
#if 1	// mit der Kneifzange
 TCHAR t[6];
 double f=exp(nk*LN10);	// Faktor für Nachkommastellen
// FormatMessage() unterstützt variable Feldbreiten mit "*"
// Aber nicht unter Win98!!
// GetNumberFormat() würde sich ebenfalls um Komma sowie Minus kümmern (Test?)
 int i=lrint(v*f);
 lstrcpy(t,T("%0*d"));
 t[2]=nk+(i<0?'2':'1');
 i=wnsprintf(s,len-1,t,i);
 if (nk) {
  int j;
  i++; nk++;
  j=i;
  do s[j]=s[j-1], j--; while(--nk);
  s[j]=sDecimal[0];
 }
#else	// mit msvcrt.dll
 int i=_sntprintf(s,len,T("%.*f"),nk,v);
 PTSTR p=StrChr(s,'.');
 if (p) *p=sDecimal[0];
#endif
 return i;
}
// MAPDATA-Zeiger um <index> Einträge vorrücken
static const MAPDATA *IterateMapData(const MAPDATA*rp, BYTE index) {
 if (rp && index) do (BYTE*)rp+=sizeof(*rp)+rp->nPoly*sizeof(rp->PolyDiff[0]); while(--index);
 return rp;
}

// Polygondaten zu einer Region (0..89) beschaffen
static const MAPDATA *GetMapData(BYTE index) {
 static HGLOBAL hMap;
 if (index<90) {
  if (!hMap) hMap=LoadResource(0,FindResource(0,MAKEINTRESOURCE(48),RT_RCDATA));
  if (hMap) return IterateMapData(LockResource(hMap),index);
 }
 return NULL;
}

int GetCityX(BYTE index) {
 if (index==90) return Config.uPos.x;
 else{
  const MAPDATA *rp=GetMapData(index);
  return rp ? rp->CityPos.x : 0;
 }
}
int GetCityY(BYTE index) {
 if (index==90) return Config.uPos.y;
 else{
  const MAPDATA *rp=GetMapData(index);
  return rp ? rp->CityPos.y : 0;
 }
}
PointF GetCityKoord(BYTE index) {
 PointF pf={0,0};
 if (index==90) {
  pf.X=(float)Config.uPos.x;
  pf.Y=(float)Config.uPos.y;
 }else{
  const MAPDATA *rp=GetMapData(index);
  if (rp) {
   pf.X=(float)rp->CityPos.x;
   pf.Y=(float)rp->CityPos.y;
  }
 }
 return UnmapMercator(pf);	// jetzt Angabe in Grad
}

/***************************************
 * Automatische Fenstergrößenanpassung *
 ***************************************/

// Struct für folgendes MonitorEnumProc()
typedef struct{
 RECT wr,work;	// wr.right=Breite, wr.bottom=Höhe, gilt aber nicht für <work>
 MONITORINFO mi;
 bool repeat;	// Horz/Vert Wachstum wurde vorgenommen
}edm_t;

// horizontal (oder vertikal bei verschobenen Zeigern) erweitern, wenn nötig
static bool extend(edm_t*E) {
// Wo grenzt er an?
 if (E->wr.right<=E->work.right-E->work.left) return false;	// Erweiterung von Interesse?
 if (E->work.left==E->mi.rcWork.right) E->work.left=E->mi.rcWork.left;	// links dransetzen
 else if (E->work.right==E->mi.rcWork.left) E->work.right=E->mi.rcWork.right;// rechts dransetzen
 else return false;
 return true;
}

// Problem: Der Algorithmus arbeitet nicht für 4 im Quadrat angeordnete Monitore!
static void clip(edm_t*E) {
 SETMAX(E->work.left,E->mi.rcWork.left);
 SETMIN(E->work.right,E->mi.rcWork.right);
}

static BOOL CALLBACK MonitorEnumProc(HMONITOR mon, HDC dc, RECT*rc, LPARAM data) {
 edm_t *E=(edm_t*)data;
 GetMonitorInfo(mon,&E->mi);
 if (extend(E)) {clip((edm_t*)&E->wr.top); E->repeat=true; return FALSE;}
// Verschobene Zeiger übergeben für die gleiche Prozedur, so arbeitet sie vertikal
 if (extend((edm_t*)&E->wr.top)) {clip(E); E->repeat=true; return FALSE;}
 return TRUE;
}

// <K> ist nicht const, weil dabei WM_SIZE verarbeitet wird
static void AdjustWindowSize(karte*K) {
 if (K->kAutoScale&4 && !IsZoomed(K->hMain) && !IsMinimized(K->hMain)) {	// Falls überhaupt
  int dx=K->ScrollInfo[SB_HORZ].nMax-K->ScrollInfo[SB_HORZ].nMin+1-ScrollRectExt(K,SB_HORZ);	// Wachstums-Wunsch des Fensters
  int dy=K->ScrollInfo[SB_VERT].nMax-K->ScrollInfo[SB_VERT].nMin+1-ScrollRectExt(K,SB_VERT);
  if (dx||dy) {
   POINT fix;
   edm_t E;
   HMONITOR mon;
   if (K->bFromMouse) fix=K->MousePos;
   else{
    fix.x=(K->ScrollRect.left+K->ScrollRect.right)>>1;	// Mitte Client-Bereich
    fix.y=(K->ScrollRect.top+K->ScrollRect.bottom)>>1;
   }
   ClientToScreen(K->hMap,&fix);
   GetWindowRect(K->hMain,&E.wr);
   DbgPrintf(("AdjustWindowSize: delta=(%d,%d), wr=(%d,%d,%d,%d), fix=(%d,%d)\n",
     dx,dy,E.wr.left,E.wr.top,E.wr.right,E.wr.bottom,fix.x,fix.y));
   E.wr.right-=E.wr.left;	// ab jetzt: Breite
   E.wr.bottom-=E.wr.top;	// ab jetzt: Höhe
   E.wr.left-=MulDiv(fix.x-E.wr.left,dx,E.wr.right);
   E.wr.top -=MulDiv(fix.y-E.wr.top,dy,E.wr.bottom);
   E.wr.right+=dx;
   E.wr.bottom+=dy;

   mon=MonitorFromWindow(K->hMain,MONITOR_DEFAULTTONEAREST);
   E.mi.cbSize=sizeof(E.mi);
   GetMonitorInfo(mon,&E.mi);
   CopyRect(&E.work,&E.mi.rcWork);
   if (K->kAutoScale&8) {	// Arbeitsbereich erweitern (nichttrivial!!)
    do{
     E.repeat=false;
     if (!EnumDisplayMonitors(0,NULL,MonitorEnumProc,(LPARAM)&E)) break;
    }while(E.repeat);	// wiederholen wenn eine Erweiterung vorgenommen wurde
   }
// Eingrenzen auf vollständige Sichtbarkeit
   E.work.right-=E.work.left;
   E.work.bottom-=E.work.top;
   SETMAX(E.wr.left,E.work.left);	// Nicht links/oben herausfallen!
   SETMAX(E.wr.top,E.work.top);		// (sonst kann Titelleiste außerhalb geraten)
   SETMIN(E.wr.right,E.work.right);	// Ausdehnung begrenzen
   SETMIN(E.wr.bottom,E.work.bottom);
   dx=E.wr.left+E.wr.right-(E.work.left+E.work.right);	// Überhang rechts
   dy=E.wr.top+E.wr.bottom-(E.work.top+E.work.bottom);	// Überhang unten
   if (dx>0) E.wr.left-=dx;
   if (dy>0) E.wr.top -=dy;
   DbgPrintf(("SetWindowPos: wr=(%d,%d,%d,%d)\n",E.wr.left,E.wr.top,E.wr.right,E.wr.bottom));
   SetWindowPos(K->hMain,0,E.wr.left,E.wr.top,E.wr.right,E.wr.bottom,SWP_NOZORDER|SWP_DRAWFRAME);
// Normalerweise sollte sich SetWindowPlacement() um alles kümmern, aber da wimmelt es von Bugs:  
// Hilfe-Aussage: kümmert sich im Gegensatz zu SetWindowPos() um Bildschirmgrenzen: nee! (W2k)
// Um die Größenbegrenzung kümmert sich SetWindowPos(), belässt aber etwas zu viel. (W2k,7)
  }
 }
}

/****************
 * Mini-Fenster *
 ****************/

// <rc> enthält Breite/Höhe statt rechts/unten
// Die linke obere Ecke wird hier „berechnet“
static void MoveMiniRect(const karte *K, RECT *rc) {
 char kMini=K->kMini;	// lokale Kopie
 switch (kMini&3) {
  case 1: if (rc->left>-32768) break;	// Position je nach Andockung berechnen
  case 2: {	// außen
   RECT wr;
   GetWindowRect(K->hMain,&wr);
   rc->left = kMini&4 ? wr.right : wr.left-rc->right;
   rc->top  = kMini&8 ? wr.bottom-rc->bottom : wr.top;
  }break;
  case 3: {	// innen
   rc->left = kMini&4 ? K->ScrollRect.right-rc->right : K->ScrollRect.left;
   rc->top  = kMini&8 ? K->ScrollRect.bottom-rc->bottom : K->ScrollRect.top;
  }break;
 }
}

// aufzurufen wenn sich hMap ändert - wegen Rollbalken, oder wenn sich ebenso deswegen K->ScrollRect ändert
static void MiniParentChange(const karte *K) {
 switch (K->kMini&3) {
  case 3: {	// innen?
   if (GetParent(K->hMini)!=K->hMap) SetParent(K->hMini,K->hMap);
   InvalidateRect(K->hMini,NULL,FALSE);	// Polygon-Eselsohr kann sich ändern
  }nobreak;
  case 2: {	// außen?
   RECT wr;
   GetWindowRect(K->hMini,&wr);
   wr.right-=wr.left;	// Höhe
   wr.bottom-=wr.top;	// Breite
   MoveMiniRect(K,&wr);	// hierfür benötigt
   SetWindowPos(K->hMini,0,wr.left,wr.top,0,0,SWP_NOSIZE|SWP_NOZORDER);
  }break;
 }
}

// Client-Position in Mini-Position
static int unscale2mini(const karte *K, int p, int nBar) {
 return MulDiv(cp2bp(K,p,nBar)-(&K->MapExtent.left)[nBar],1,K->MiniScale);
}

static void SaveMiniPos(karte *K) {
 if ((K->kMini&3)==1) {
  RECT wr;
  GetWindowRect(K->hMini,&wr);
  K->kMiniPos=*(POINT*)&wr;
  if (K->hMain==hFirst) {
   Config.kMiniPos.x=(short)wr.left;
   Config.kMiniPos.y=(short)wr.top;
  }
 }
}

/**********************
 * Menü-Hilfsroutinen *
 **********************/

static void PrepareMenu(karte *K, HMENU m) {
 int TagNr;
 SetCheckMenuGroup(m,0x10,0x17,K->kShow);
 CheckMenuRadioItem(m,0x20,0x23,0x20+(K->kMini&3),0);		// „Miniatur“: Darstellungsform
 CheckMenuRadioItem(m,0x24,0x27,0x24+(K->kMini>>2&3),0);	// Position
 EnableMenuGroup(m,0x24,0x27,K->kMini&2?MF_ENABLED:MF_GRAYED);	// Positionen ausgrauen
 CheckMenuItem(m,0x28,K->kMini&0x10?MF_CHECKED:MF_UNCHECKED);
 EnableMenuItem(m,0x28,K->kMini&3?MF_ENABLED:MF_GRAYED);
 EnableMenuItem(m,0x2A,K->kMini&3&&(K->kMini>>5<3)?MF_ENABLED:MF_GRAYED);
 EnableMenuItem(m,0x2B,K->kMini&3&&(K->kMini>>5>-4)?MF_ENABLED:MF_GRAYED);
 EnableMenuItem(m,0x30,K->pPicture?MF_ENABLED:MF_GRAYED);	// „Bitmap“ ausgrauen
 SetCheckMenuGroup(m,0x30,0x37,K->kVis);			// „Karte“: alle 8 Bits
 CheckMenuItem(m,0x44,K->Scale.Z==K->Scale.N?MF_CHECKED:MF_UNCHECKED);
 EnableMenuItem(m,0x44,K->Scale.Z==K->Scale.N?MF_GRAYED:MF_ENABLED);
 SetCheckMenuGroup(m,0x50,0x53,K->kAutoScale);			// ZoomBreite/ZoomHöhe/AutoGrow/AutoShrink
 EnableMenuItem(m,0x53,GetSystemMetrics(SM_CMONITORS)>1?MF_ENABLED:MF_GRAYED);
 CheckMenuRadioItem(m,0x58,0x5B,0x58+(K->kAutoScale>>4&3),0);	// Schriftgröße
 CheckMenuRadioItem(m,0x5C,0x5F,0x5C+(K->kAutoScale>>6&3),0);	// Wettersymbolgröße
 CheckMenuRadioItem(m,0x60,0x6F,0x60+(K->kWetter&15),0);
 for (TagNr=0; TagNr<4; TagNr++) {
  MENUITEMINFO mii;
  TCHAR s[32];
  HMENU sm=GetSubMenu(m,4);
  if (!sm) sm=GetSubMenu(m,1);			// wenn Kontextmenü ausgedünnt
  TagesName(TagNr,s,elemof(s),sm);
  mii.cbSize=sizeof(mii);
  mii.fMask=MIIM_STRING;
  mii.dwTypeData=s;				// passenden Wochentag einsetzen
  SetMenuItemInfo(m,0x70+TagNr,FALSE,&mii);	// ModifyMenu() lässt Bitmap verschwinden
 }
 CheckMenuRadioItem(m,0x70,0x73,0x70+(K->kWetter>>4&3),0);
 EnableMenuGroup(m,0x70,0x73,K->kWetter&15?MF_ENABLED:MF_GRAYED);	// Tage ausgrauen
 SetCheckMenuGroup(m,0x74,0x75,K->kWetter>>6);	// Gong + automatisches Update
 EnableMenuItem(m,0x75,GetPropWetter()?MF_ENABLED:MF_GRAYED);
// GenSliderMenu(m,0x54);
// GenSliderMenu(m,0x55);
}

static int BitmapUsage;
static HBITMAP Bitmaps[12];

static void AddBitmaps(HMENU m, bool force) {
 int i;
 MENUITEMINFO mii;
 if (!BitmapUsage++ || force) {
  HDC odc=GetDC(0);
  HDC dc=CreateCompatibleDC(odc);
//  HDC sdc=CreateCompatibleDC(dc);
//  HBITMAP sbm=SelectBitmap(sdc,LoadBitmap(ghThisInst,MAKEINTRESOURCE(2)));
  HIMAGELIST il=ImageList_LoadBitmap(ghThisInst,MAKEINTRESOURCE(2),16,0,RGB(192,192,192));
  HBITMAP obm=GetCurrentObject(dc,OBJ_BITMAP);
  HBRUSH obr=GetSysColorBrush(COLOR_MENU);
  int bmwidth=20;
#if 1
  BOOL flat=FALSE;
  SystemParametersInfo(0x1022/*SPI_GETFLATMENU*/,0,&flat,0);
  if (flat) {
   bmwidth=16;
#if 0
   MENUINFO mi;
   mi.cbSize=sizeof(mi);
   mi.fMask=MIM_BACKGROUND;
   GetMenuInfo(m,&mi);
   obr=mi.hbrBack;	// Hier kommt Murks (weiß) heraus, wenn ohne Teletubbieoptik
#endif
  }
#endif
  obr=SelectBrush(dc,obr);
  for (i=0; i<elemof(Bitmaps); i++) {
   SelectBitmap(dc,Bitmaps[i]=CreateCompatibleBitmap(odc,bmwidth,15));
   PatBlt(dc,0,0,bmwidth,15,PATCOPY);
//   TransparentBlt(dc,0,0,16,15,sdc,i<<4,0,16,15,RGB(192,192,192));
   ImageList_Draw(il,i,dc,0,0,0);
  }
  ReleaseDC(0,odc);
  SelectBitmap(dc,obm);
  SelectBrush(dc,obr);
//  DeleteBitmap(SelectBitmap(sdc,sbm));
  ImageList_Destroy(il);
  DeleteDC(dc);
//  DeleteDC(sdc);
 }
 mii.cbSize=sizeof(mii);
 mii.fMask=MIIM_BITMAP;
 for(i=0; i<elemof(Bitmaps); i++) {
  mii.hbmpItem=Bitmaps[i];
  SetMenuItemInfo(m,0x60+(i&8)+i,FALSE,&mii);
 }
}

static void DelBitmaps(bool force) {
 if (!--BitmapUsage || force) {
  int i;
  for (i=0; i<elemof(Bitmaps); i++) DeleteBitmap(Bitmaps[i]);
 }
 if (BitmapUsage<0) DbgPrintf(("DelBitmaps: BUG: BitmapUsage negativ! (%d)\n",BitmapUsage));
}

/*********************
 * GUI-Hilfsroutinen *
 *********************/

static void InitToolInfo(const karte*K, TOOLINFO *ti) {
 InitStruct(ti,sizeof(*ti));
 ti->uFlags=TTF_TRACK;
 ti->hwnd=K->hMain;
 ti->uId=1;
 ti->hinst=ghThisInst;
 ti->lpszText=LPSTR_TEXTCALLBACK;
}

static void TrackActivate(const karte*K, BOOL fAct) {
 TOOLINFO ti;
 InitToolInfo(K,&ti);
 SendMessage(K->hTip,TTM_TRACKACTIVATE,fAct,(LPARAM)&ti);
}

/**************************
 * Variable ändern: kShow *
 **************************/

static void AddToolbar(karte*K) {
 TBBUTTON buttons[14],*b=buttons;
 int i;
 __stosd((DWORD*)buttons,0,sizeof(buttons)/4);
 for (i=0; i<12; i++,b++) {
  if (i==0 || i==8) b++->fsStyle=TBSTYLE_SEP;
  b->iBitmap=i;
  b->idCommand=0x60+(i&8)+i;
  b->fsState=TBSTATE_ENABLED;
  b->fsStyle=BTNS_CHECKGROUP;
  b->iString=(INT_PTR)-1;
 }
 K->hToolbar=CreateToolbarEx(K->hMain,WS_VISIBLE|WS_CHILD|TBSTYLE_TOOLTIPS,10,NUMSTAAT,ghThisInst,2,
   buttons,elemof(buttons),16,16,16,15,sizeof(TBBUTTON));
}

static void SetToolbarButtons(const karte*K) {
 SendMessage(K->hToolbar,TB_CHECKBUTTON,0x60+(K->kWetter&7),TRUE);
 SendMessage(K->hToolbar,TB_CHECKBUTTON,0x70+(K->kWetter>>4&3),TRUE);
}

static void kShowChange(karte *K, BYTE xor) {
 BYTE o=K->kShow;
 BYTE n=K->kShow=o^xor;
 bool o2,n2;		// 2 Fenster (hMap!=hMain) alt + neu
 if (!xor) xor=o, o=0;	// Sonderfall Initialisierung
 o2 = o&0x60 && o&0x0C;
 n2 = n&0x60 && n&0x0C;
 if (xor&0x04) ShowScrollBar(K->hMap,SB_HORZ,n&0x04);
 if (xor&0x08) ShowScrollBar(K->hMap,SB_VERT,n&0x08);
 if (n2 && !o2) ShowScrollBar(K->hMain,SB_BOTH,FALSE);
// if (!SetWindowTheme(K->hMain,NULL,NULL)) UpdateWindow(K->hMain);	// angeblich Vista/7-Bugfix
 if (xor&0x10) {
  if (n&0x10) {
   HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
   DeleteMenu(m,5,MF_BYPOSITION);	// Niemals "Meine Position" im Hauptmenü
   AddBitmaps(GetSubMenu(m,4),false);
   if (!HelpName[0]) DeleteMenu(m,5,MF_BYPOSITION);	// Keine "Hilfe" ohne Hilfedatei
   SetMenu(K->hMain,m);
  }else{
   DestroyMenu(GetMenu(K->hMain));
   DelBitmaps(false);
   SetMenu(K->hMain,0);
  }
 }
 if (xor&0x20) {
  if (n&0x20) {
   AddToolbar(K);
   SetToolbarButtons(K);
  }else{
   DestroyWindow(K->hToolbar);
   K->hToolbar=0;	// sollte unnötig sein, wenn man auf kShow testet
  }
 }
 if (xor&0x40) {
  if (n&0x40) {
   static const int StatusDiv[]={40,170,300,550,-1};
   K->hStatus=CreateStatusWindow(WS_VISIBLE|WS_CHILD|SBARS_SIZEGRIP,NULL,K->hMain,11);
   SendMessage(K->hStatus,SB_SETPARTS,elemof(StatusDiv),(LPARAM)StatusDiv);
   StatusScale(K);
   StatusGeo(K);
   StatusStaat(K);
   StatusRegion(K);
   StatusWetter(K);
  }else{
   DestroyWindow(K->hStatus);
   K->hStatus=0;
  }
 }
 if (xor&0x60) {
  PostMessage(K->hMain,WM_SIZE,SIZE_RESTORED,0);
  InvalidateRect(K->hMain,NULL,TRUE);
 }
 if (xor&0x80) {
  if (n&0x80) {
   TOOLINFO ti;
   K->hTip=CreateWindowEx(WS_EX_TOPMOST,TOOLTIPS_CLASS,NULL,
     WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP,
     0,0,0,0,K->hMain,NULL,ghThisInst,NULL);
   SendMessage(K->hTip,TTM_SETMAXTIPWIDTH,0,1000);
   InitToolInfo(K,&ti);
   SendMessage(K->hTip,TTM_ADDTOOL,0,(LPARAM)&ti);	// erscheint bei Mausbewegung
  }else{
   DestroyWindow(K->hTip);
   K->hTip=0;
  }
 }
// Anwesenheitswechsel des Kindfensters detektieren
 if (n2^o2) {
  if (n2) {	// Neues Kindfenster?
   RECT rc;
   LoadClientRect(K->hMain,&rc);
   K->hMap=CreateWindow(KARTECLASSNAME,NULL,WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN,
     rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,
     K->hMain,(HMENU)12,ghThisInst,K);
  }else{
   if (K->hMini && GetParent(K->hMini)==K->hMap) SetParent(K->hMini,K->hMain);
   DestroyWindow(K->hMap);
   K->hMap=K->hMain;
  }
  SetFocus(K->hMap);
 }
 if (!n2 && o2) {
  if (K->kShow&4) SetScrollInfo(K->hMain,SB_HORZ,K->ScrollInfo+SB_HORZ,TRUE);
  if (K->kShow&8) SetScrollInfo(K->hMain,SB_VERT,K->ScrollInfo+SB_VERT,TRUE);
 }
 LoadClientRect(K->hMap,&K->ScrollRect);
 if (n2^o2) MiniParentChange(K);
 if (K->hMain==hFirst) Config.kShow=K->kShow;
}

// Liefert Soll-Größe für Client-Bereich
static SIZE MiniExtent(const karte *K) {
 SIZE ext={MapExtentExt(K,SB_HORZ)/K->MiniScale,MapExtentExt(K,SB_VERT)/K->MiniScale};
 return ext;
}

/**************************
 * Variable ändern: kMini *
 **************************/

static void CalcMiniScale(karte*K) {
 int x,y,b;
 x=MulDiv(MapExtentExt(K,SB_HORZ),8,GetSystemMetrics(SM_CXFULLSCREEN));
 y=MulDiv(MapExtentExt(K,SB_VERT),8,GetSystemMetrics(SM_CYFULLSCREEN));
 SETMAX(x,y);
 _BitScanReverse(&b,x);	// svw. »logarithmus dualis«
 b=(b<<1)+(x>>(b-1)&1);	// Nächstes Bit mitnehmen
 b+=K->kMini>>5;	// Benutzer-Skalierung dazu
 SETMAX(b,0);
 x=(2|b&1)<<(b>>1);	// Exponentialfunktion mit je 1 linearer Zwischenstufe
 DbgPrintf(("CalcMiniScale: %d\n",x));
 K->MiniScale=(char)x;
}

static void ShowMini(const karte*K) {
 if (K->hMini) {
  ShowWindow(K->hMini,!(K->kMini&0x10)
  || (int)K->ScrollInfo[SB_HORZ].nPage<=K->ScrollInfo[SB_HORZ].nMax-K->ScrollInfo[SB_HORZ].nMin
  || (int)K->ScrollInfo[SB_VERT].nPage<=K->ScrollInfo[SB_VERT].nMax-K->ScrollInfo[SB_VERT].nMin
  ?SW_SHOWNA:SW_HIDE);
 }
}

static void kMiniSet(karte *K, char n, char mask) {
 char o=K->kMini;
 char xor;
 n=o&~mask|n&mask;
 xor=n^o;
 if (!mask) xor=o, o=0;	// Sonderfall Initialisierung
 if (xor&0xE3 && o&3) {	// alter Zustand
  DestroyWindow(K->hMini);	// kann K->kMini verändern!
  if (xor&0xE0 && K->MiniBmp && DeleteBitmap(K->MiniBmp)) K->MiniBmp=0;
 }
 K->kMini=n;
 if (xor&0xE0) CalcMiniScale(K);
 if (xor&0x10) ShowMini(K);
 if (K->hMain==hFirst) {
  Config.kMini=n;
 }
 if (xor&0xE3 && n&3) {
  static const WORD styles[3]={
    (WS_BORDER|WS_CAPTION|WS_SYSMENU)>>16,
    (WS_POPUP|WS_BORDER|WS_THICKFRAME)>>16,		// müssen WM_SIZE und WM_NCHITTEST abfangen
    (WS_CHILD|WS_BORDER|WS_VISIBLE|WS_CLIPSIBLINGS)>>16};	// ClipSiblings: Statuszeile
  DWORD style=styles[(n&3)-1]<<16;
  const DWORD exstyle=WS_EX_TOOLWINDOW|WS_EX_WINDOWEDGE;
  RECT rc;
  SIZE sz=MiniExtent(K);
  HMENU m;
  TCHAR s[32];
  SetRect(&rc,0,0,sz.cx,sz.cy);
  AdjustWindowRectEx(&rc,style,FALSE,exstyle);
  rc.right-=rc.left;	// Breite wiederherstellen
  rc.bottom-=rc.top;
  *(POINT*)&rc=K->kMiniPos;	// enthält zunächst -32768 (Win16 CW_USEDEFAULT)
  MoveMiniRect(K,&rc);
  m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
  GetMenuString(m,1,s,elemof(s),MF_BYPOSITION);
  StripAmpersand(s);
  K->hMini=CreateWindowEx(exstyle,KARTECLASSNAME,s,style,
  rc.left,rc.top,rc.right,rc.bottom,
  ~n&3?K->hMain:K->hMap,0,ghThisInst,K);
  switch (n&3) {
   case 1: {
    AnimateWindow(K->hMini,200,AW_BLEND);
   }break;
   case 2: {
    AnimateWindow(K->hMini,200,n&4?AW_SLIDE|AW_HOR_POSITIVE:AW_SLIDE|AW_HOR_NEGATIVE);
//   ShowWindow(K->hMini,SW_SHOWNA);	// PROBLEM: Beim Starten der Karte wird dieses Fenster dennoch aktiv!
   }break;
  }
 }else if (xor&0x0C) MiniParentChange(K);	// (SW_SHOWNOACTIVE tut's nicht.)
}

/**********************************************
 * Variable ändern: kVis, kAutoScale, kWetter *
 **********************************************/

static void kVisChange(karte *K, BYTE xor) {
 K->kVis^=xor;
 if (K->hMain==hFirst) Config.kVis=K->kVis;
 InvalidateRect(K->hMap,&K->ScrollRect,TRUE);
 InvalSymbChange(K,90);
}

// n = neue Bits, mask = maskiert die neuen Bits, xor = dreht Bits
static void kAutoScaleSet(karte*K,BYTE n, BYTE mask, BYTE xor) {
 n=(K->kAutoScale&~mask|n&mask)^xor;	// sich ergebender neuer Wert
 xor=n^K->kAutoScale;			// sich ändernde Bits
 if (!xor) return;
 K->kAutoScale=n;
 if (K->hMain==hFirst) Config.kAutoScale=n;
 if (n&xor&0x0C) AdjustWindowSize(K);	// wenn eins der Bits /gesetzt/ wird
 if (xor&0xF0) InvalSymbChange(K,-1);
}
// n = neue Bits, mask = maskiert die neuen Bits, xor = dreht Bits
static void kWetterSet(karte *K, BYTE n, BYTE mask, BYTE xor) {
 n=(K->kWetter&~mask|n&mask)^xor;	// sich ergebender neuer Wert
 xor=n^K->kWetter;			// sich ändernde Bits
 if (xor) {
  K->kWetter=n;
  if (K->hMain==hFirst) Config.kWetter=n;
  if (xor&0x3F) {
   InvalSymbChange(K,-1);
   if (K->kShow&0x40) StatusWetter(K);
  }
 }
 if (!mask && !xor) {
  xor=0xFF;		// Initialisierungsfall
  InvalSymbChange(K,-1);
 }
 if (K->hToolbar && xor&0x3F) SetToolbarButtons(K);
}

static void SetScale(karte *K, char kode) {
 char kScale=K->kScale;
 char kAutoScale=K->kAutoScale;
 if (kode>=0 && kode!=66) {
  char lineadd=GetKeyState(VK_SHIFT)<0?1:4;	// Kleine Schritte mit gedrückter Shift-Taste
  kAutoScale=0;
  switch (kode) {
   case SB_LINELEFT: kScale-=lineadd; break;
   case SB_LINERIGHT: kScale+=lineadd; break;
   case SB_PAGELEFT: kScale-=16; break;
   case SB_PAGERIGHT: kScale+=16; break;
   case 100: kScale=0; break;			// 1:1
  }
  SETMIN(kScale,64);	// begrenzen auf 2^4  = 16fach
  SETMAX(kScale,-64);	// begrenzen auf 2^-4 = 1/16
  K->Scale.N=1<<((128-kScale)>>4);		// ein rationales Verhältnis draus machen
  K->Scale.Z=lrint(exp(kScale/(LOG2E*16))*K->Scale.N);
 }else{
  int nBar=SB_HORZ;	// 0
  if (kode==66) goto beide;	// in Fenster einpassen, aber nicht kAutoScale setzen
  kAutoScale=-kode;	// -1, -2 oder -3
  switch (kAutoScale&3) {
   case 3: beide: 		// dort, wo's passt!
   if ((double)MapExtentExt(K,SB_VERT)/ScrollRectExt(K,SB_VERT)
    < (double)MapExtentExt(K,SB_HORZ)/ScrollRectExt(K,SB_HORZ)) break;
   case 2: nBar=SB_VERT;
  }
  K->Scale.Z=ScrollRectExt(K,nBar);
  K->Scale.N=MapExtentExt(K,nBar);
  kScale=lrint(log((double)K->Scale.Z/K->Scale.N)*(LOG2E*16));	// kScale informativ mitführen
 }
 K->kScale=kScale;
 if (K->hMain==hFirst) {
  Config.kScale=kScale;
 }
 kAutoScaleSet(K,kAutoScale,3,0);
 kVisChange(K,0);
 AdjustScrollRange(K);
 ShowMini(K);
 AdjustWindowSize(K);
 CalcRects(K);
 InvalSymbChange(K,-1);
 if (K->kShow&0x40) StatusScale(K);
}

/******************
 * TAB-Behandlung *
 ******************/

// Low-Nibble = Vorhersagetyp (1..7)
// High-Nibble = Tages-Nummer (0..3)
static int CountSymbols(BYTE n) {
 int k,r=0;
 for (k=0; k<90; k++) {
  U64 v=GetTagWetter(n,k);
  if (v.ull) {
   switch (n&15) {
    case 1:
    case 3: r++; break;	// Tag oder Nacht
    case 2:
    case 5: if (v.Tag) r++; break;	// nur Tag
    case 4:
    case 6: if (v.Nacht) r++; break;	// nur Nacht
    case 7: if (!(v.Tag&0x8000) && v.Tag&0x0F00) r++; break;
   }
  }
 }
 return r;
}

// liefert TRUE wenn mindestens ein Wettersymbol angezeigt werden kann,
// für Leertaste, Menü(?) und Buttonleiste
// Low-Nibble = Vorhersagetyp
// High-Nibble = Tages-Nummer
static bool AnySymbol(BYTE n) {
 n&=0x3F;
 if ((BYTE)((n&0x0F)-1)>=6) return false;
 switch (n) {
  case 0x34:
  case 0x36: return false;
 }
 return CountSymbols(n);
}

// Zum Durchschalten mit Buchstabentaste (nur Anfangsbuchstabe)
// (nicht wie im Explorer durch Eintippen der Anfangsbuchstaben in einem Zeitfenster)
static bool MatchStadt(int k, TCHAR match) {
 if (!match) return true;
 {TCHAR s[64], *sp=s;
  int rl=LoadString(ghInstance,256+k,s,elemof(s));
  int sl=lstrlen(s);
  if (sl<rl) sp+=sl+1;	// tatsächlich zweiteilig? Zeiger auf die Stadt setzen
  return !StrCmpNI(sp,&match,1);	// Anfangsbuchstabe der angezeigten Stadt
 }
}

// Ziehe Rechteck in Mitte; dabei rc in unverschobenen Client-Koordinaten und pt (Schwerpunkt) in Bitmap-Koordinaten
// Für TAB = Durchschalten der Regionen sowie (TODO) Länder-Auswahl
static int MoveRectInto(const karte *K, const RECT *rc, const POINT *pt, int nBar) {
 int t;
 int l=(&rc->left)[nBar];
 int r=(&rc->right)[nBar];
 int L=(&K->ScrollRect.left)[nBar];
 int R=(&K->ScrollRect.right)[nBar];
 if (r-l > R-L) {	// passt nicht hinein
  int M=(L+R)>>1;
  int n=bp2cp(K,(&pt->x)[nBar],nBar);
  return M-n;		// kann 0 sein
 }
 t=shift(K,nBar);
 l+=t;
 r+=t;
 if (l<L) return L-l;	// nach rechts
 if (r>R) return R-r;	// nach links
 return 0;
}

// Macht das Polygon-Umfangsrechteck sichtbar, indem es in das Fenster gezogen wird.
// Passt es nicht hinein, wird am Schwerpunkt zentriert.
// Wenn's vollständig sichtbar ist, tut diese Funktion nichts.
// Hier: Berechnung der notwendigen Scrolldistanz in Pixel
static int MoveInto(const karte *K, int k, int nBar) {
 const MAPMEM *m=K->map[k];
 return MoveRectInto(K,&m->rcPoly,&m->Centroid,nBar);
}

static void FocusRegion(karte*K, int k) {
 HandleShift(K,MoveInto(K,k,SB_HORZ),MoveInto(K,k,SB_VERT));
 SetHoverIndex(K,(char)k);	// es funktioniert nicht "smooth", wenn man die Reihenfolge vertauscht!!
}

// Die Tab-Taste setzt die Hover-Funktion der Maus temporär außer Kraft,
// und die numerisch nächste/vorhergehende Region wird fokussiert
// Großbuchstabe (Shift gedrückt) = rückwärts, Kleinbuchstabe (Bit 5 gesetzt) vorwärts
static void HandleTab(karte *K, TCHAR suche) {
 char k=K->HoverIndex;
 char start=k;
 char e=Config.uRegion>=0?91:90;
 char dir=GetKeyState(VK_SHIFT)<0?-1:1;
 if (start<0) start=dir>=0?e-1:0;
 for(;;){
  k+=dir;
  if (k<0) k=e-1;
  if (k>=e) k=0;	// umlaufend
  if (MatchStadt(k,suche)) break;	// Nicht piepsen wenn genau eine Stadt mit dem Buchstaben zugeordnet ist
  if (k==start) {MessageBeep(MB_ICONSTOP); return;}
 }
 FocusRegion(K,k);
}

// "Meine Position" von Karte (und nur dort) löschen
static void deleteMyPos(karte *K) {
 MAPMEM *m=K->map[90];
 if (m->nPoly) {
  InvalIndex(K,90);			// alte Region invalidieren (Kreuz wegnehmen)
  InvalRect(K,&K->map[90]->rcText);	// den Text auch!
 }
 m->nPoly=0;
}

/*************************************
 * Fensterprozedur (Mehrfachnutzung) *
 *************************************/

static LRESULT WINAPI MainWndProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 int i;
 karte *K=(karte*)GetWindowLongPtr(Wnd,0);
 if (msg==WM_NCCREATE) {
  CREATESTRUCT *cs=(CREATESTRUCT*)lParam;
  if (cs->lpCreateParams) {	// Kartenkind, Miniatur
   K=cs->lpCreateParams;
   if (cs->style&WS_CHILD && !(cs->style&WS_BORDER)) K->hMap=Wnd;	// Kartenkind
   else K->hMini=Wnd;		// Miniatur
  }else{			// allererste Amtshandlung, sonst kracht's
   K=LocalAlloc(LPTR,sizeof(karte)-sizeof(BORDEREX)+CalcBorderexSize());
   if (K) {
    K->hMap=K->hMain=Wnd;
    K->kShow=Config.kShow;	// Jedes der möglichen Karten-Fenster erbt die gleichen Voreinstellungen.
    K->kMini=Config.kMini;	// Diese können getrennt verändert werden.
    K->kVis=Config.kVis;	// Aber nur das primäre Karten-Fenster (hFirst) speichert die Einstellungen.
    K->kScale=Config.kScale;
    K->kAutoScale=Config.kAutoScale;
    K->kWetter=Config.kWetter;
    K->ScrollInfo[SB_VERT].cbSize=K->ScrollInfo[SB_HORZ].cbSize=sizeof(SCROLLINFO);
    K->HoverIndex=-1;
    K->HoverStaat=-1;
    K->MyPosStaat=-1;
    K->kMiniPos.x=Config.kMiniPos.x;	// Freies Mini-Fenster irgendwohin platzieren (wirkt nicht, deshalb Sonderbehandlung!)
    K->kMiniPos.y=Config.kMiniPos.y;
   }
  }
  SetWindowLongPtr(Wnd,0,(LONG_PTR)K);
 }
 if (!K) return DefWindowProc(Wnd,msg,wParam,lParam);
 
/*** (0) Nachrichtenbehandlung für alle Fenster ***/
 K->bFromMouse=false;
 if (WM_MOUSEFIRST<=msg && msg<=WM_MOUSELAST) {
  K->bFromMouse=true;
  if (LOWORD(wParam)) SetCapture(Wnd); else ReleaseCapture();
 }
/*** (A) Kartenkind ***/
 if (Wnd==K->hMap && Wnd!=K->hMain) switch (msg) {
  case WM_NCDESTROY: K->hMap=K->hMain; break;
/*** (B) Miniatur ***/
 }else if (Wnd==K->hMini) switch (msg) {
  case WM_CREATE: {
   if (!K->MiniBmp) {
    SIZE sz=MiniExtent(K);
    HDC dc=GetDC(Wnd);
    HDC memdc=CreateCompatibleDC(dc);
    HBITMAP obm;
    K->MiniBmp=CreateCompatibleBitmap(dc,sz.cx,sz.cy);
    ReleaseDC(Wnd,dc);
    obm=SelectBitmap(memdc,K->MiniBmp);
    PatBlt(memdc,0,0,sz.cx,sz.cy,PATCOPY);
    SetMapMode(memdc,MM_ANISOTROPIC);
    SetWindowExtEx(memdc,K->MiniScale,K->MiniScale,NULL);
    SetWindowOrgEx(memdc,K->MapExtent.left,K->MapExtent.top,NULL);
    PaintPicture(K,memdc);
    SelectBitmap(memdc,obm);
    DeleteDC(memdc);
   }
  }break;
  case WM_CLOSE: {	// Klick aufs rote X
   SaveMiniPos(K);
   K->kMini&=~3;
   if (K->hMain==hFirst) Config.kMini=K->kMini;
  }break;
  case WM_DESTROY: SaveMiniPos(K); break;
  case WM_NCDESTROY: K->hMini=0; break;		// Referenz vernichten
  case WM_LBUTTONDOWN: {
   K->MousePos.x=GET_X_LPARAM(lParam);
   K->MousePos.y=GET_Y_LPARAM(lParam);
  }return 0;
  case WM_MOUSEMOVE: if (wParam&MK_LBUTTON) {
   POINT n={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
   POINT d;	// Delta
   d.x=n.x-K->MousePos.x;
   d.y=n.y-K->MousePos.y;
   K->MousePos=n;
   if (HandleShift(K,scale(K,d.x*-K->MiniScale),scale(K,d.y*-K->MiniScale)))
     UpdateWindow(Wnd);	// vorrangig behandeln: WM_PAINT senden für direktes Feedback
  }return 0;
  case WM_CONTEXTMENU: {
   HMENU m=CreateMenu();
   HMENU sm=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
   int i;
   for (i=0;i<6; i++) DeleteMenu(sm,i?1:0,MF_BYPOSITION);	// Nur "Miniatur" stehen lassen
   PrepareMenu(K,sm);
   InsertMenu(m,0,MF_BYPOSITION|MF_POPUP,(UINT)sm,NULL);	// sonst geht's nicht!
   TrackPopupMenu(sm,TPM_RIGHTBUTTON,
     GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),0,K->hMain,NULL);
   DestroyMenu(m);
  }return 0;
  case WM_SIZE: {	// Client-Größe prüfen und Fenster ggf. ausdehnen
   SIZE ist={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
   SIZE soll=MiniExtent(K);
   if (*(__int64*)&soll!=*(__int64*)&ist) {	// Veränderung?
    RECT wr;
    GetWindowRect(Wnd,&wr);
    wr.right+=soll.cx-ist.cx-wr.left;	// neue Größe
    wr.bottom+=soll.cy-ist.cy-wr.top;
    MoveMiniRect(K,&wr);
    SetWindowPos(Wnd,0,wr.left,wr.top,wr.right,wr.bottom,SWP_NOZORDER);
   }
  }return 0;
  case WM_PRINTCLIENT: {
   RECT rc;
   HDC dc=(HDC)wParam;
   SetGraphicsMode(dc,GM_ADVANCED);
   BltBitmap(dc,K->MiniBmp);
   SetRect(&rc,	// Sichtbaren Ausschnitt umrechnen
     unscale2mini(K,K->ScrollRect.left,SB_HORZ),
     unscale2mini(K,K->ScrollRect.top,SB_VERT),
     unscale2mini(K,K->ScrollRect.right,SB_HORZ)-2,
     unscale2mini(K,K->ScrollRect.bottom,SB_VERT)-2);
   SelectPen(dc,gdi.p[4]);
   SelectBrush(dc,GetStockObject(HOLLOW_BRUSH));
   if ((K->kMini&3)==3) {	// bei Innen-Andockung „Eselsohr“ anbasteln
    static const DWORD PolyGen[4]={	//0=rc.left,1=rc.right,2=wr.left,3=wr.right
      0x8A23FC,		// 0333 3010 1101
      0x8AED10,		// 0020 2313 1101
      0x47BA20,		// 0010 1131 3202
      0x895620};	// 0010 1222 2101
    RECT wr;
    POINT poly[6];
    DWORD pg=PolyGen[(K->kMini>>2)&3];
    GetWindowRect(Wnd,&wr);
    MapWindowPoints(0,K->hMap,(POINT*)&wr,2);
    SetRect(&wr,
      unscale2mini(K,wr.left,SB_HORZ),
      unscale2mini(K,wr.top,SB_VERT),
      unscale2mini(K,wr.right,SB_HORZ)-2,
      unscale2mini(K,wr.bottom,SB_VERT)-2);
    for (i=0; i<6; i++) {
     poly[i].x = (pg&1?&wr.left:&rc.left)[pg&2?2:0];
     poly[i].y = (pg&4?&wr.top:&rc.top)[pg&8?2:0];
     pg>>=4;
    }
    Polygon(dc,poly,6);
   }else{
    Rectangle(dc,rc.left,rc.top,rc.right,rc.bottom);
   }
  }return 0;
  case WM_PAINT: {
   PAINTSTRUCT ps;
   BeginPaint(Wnd,&ps);
   SendMessage(Wnd,WM_PRINTCLIENT,(WPARAM)ps.hdc,PRF_CLIENT);
   EndPaint(Wnd,&ps);
  }return 0;
  case WM_NCHITTEST: {
   LRESULT r=DefWindowProc(Wnd,msg,wParam,lParam);
   switch (K->kMini&3) {
    case 1: if (r==HTBORDER) r=HTCAPTION; break;	// Rand zum Verschieben benutzen
    case 2: if (r!=HTCLIENT) r=HTNOWHERE; break;	// Nichts ist verschieblich
   }
   return r;
  }
  case WM_MOUSEACTIVATE: {
   if ((K->kMini&3)==2) return MA_NOACTIVATE;
  }break;
/*** (C) Nachrichtenbehandlung als Hauptfenster ***/
 }else switch (msg) {
  case WM_CREATE: {
   const MAPDATA *rp;
   const BORDER *bo;
   TCHAR buf[MAX_PATH];
   int k,i;
   if (!hFirst) {
    WINDOWPLACEMENT wp;
    hFirst=Wnd;
    wp.length=sizeof(wp);
    GetWindowPlacement(Wnd,&wp);
    if (Config.kShow&3) wp.showCmd=Config.kShow&3;
    if (Config.kPos.right-Config.kPos.left>=64
    && Config.kPos.bottom-Config.kPos.top>=64) {
     wp.rcNormalPosition.left  =Config.kPos.left;	// Kümmert sich Windows hier um die Darstellbarkeit??
     wp.rcNormalPosition.top   =Config.kPos.top;	// Angeblich ja ...
     wp.rcNormalPosition.right =Config.kPos.right;
     wp.rcNormalPosition.bottom=Config.kPos.bottom;
    }
    SetWindowPlacement(Wnd,&wp);
   }
   K->kInfo=&KINFO_NONE;	// hier kein Null-Zeiger
   if (GetKeyState(VK_CONTROL)>=0) {
    LocateFile(buf,KINFO_EUROPA.name);
    if (buf[0] && (K->pPicture=LoadPictureFile(buf))) {
     K->kInfo=&KINFO_EUROPA;
     goto done;
    }
   }
   LocateFile(buf,KINFO_EUROPE.name);
   if (buf[0] && (K->hPicture=pngload(buf))) {
    K->kInfo=&KINFO_EUROPE;
    K->bPictureHBM=true;
    goto done;
   }
   LocateFile(buf,KINFO_ECLIPSE.name);
   if (buf[0] && (K->pPicture=LoadPictureFile(buf))) {
    K->kInfo=&KINFO_ECLIPSE;
   }
done:
   SetRect(&K->MapExtent,0,0,K->kInfo->szBitmap.cx,K->kInfo->szBitmap.cy);

   rp=GetMapData(0);
   for (k=0; k<90; k++) {
    POINT start;
    K->map[k]=(MAPMEM*)LocalAlloc(LPTR,sizeof(MAPMEM)+rp->nPoly*sizeof(POINT));
    K->map[k]->nPoly=rp->nPoly;
    K->map[k]->CitySize=rp->CitySize&0x0F;
    //K->map[k]->TextPos=4;//rp->CitySize>>4;
    start.x=rp->CityPos.x;
    start.y=rp->CityPos.y;
    K->map[k]->CityPos=MapCoord(K,&start);
    for (i=0; i<rp->nPoly; i++) {
     POINT p;
     start.x+=rp->PolyDiff[i].x;
     start.y+=rp->PolyDiff[i].y;
     K->map[k]->Poly[i]=p=MapCoord(K,&start);
     InflateRectForPoint(&K->MapExtent,&p);
    }
    rp=IterateMapData(rp,1);
   }

   bo=GetBorder();
   if (bo) {
    BORDEREX *boe=&K->bo;
    for (; bo->n; bo=IterateBorder(bo), boe=IterateBorderex(boe)) {
     int i;
     POINT p={bo->start.x,bo->start.y},*pt=boe->points;
     const POINTC *ptc=bo->Diff;
     boe->hdr=bo->hdr;	// Header 1:1 kopieren
     for(i=bo->n;;pt++,ptc++) {
      *pt=MapCoord(K,&p);	// Punkt umrechnen (langsam!)
      if (!--i) break;
      p.x+=ptc->x;		// Nächste Differenz nehmen
      p.y+=ptc->y;
     }
    }
    boe->valid=0;	// eigentlich unnötig, da Speicher ausgenullt
    CreateStaatenRegions(K);
   }

   K->map[90]=(MAPMEM*)LocalAlloc(LPTR,sizeof(MAPMEM)+12*sizeof(POINT));
   if (Config.uRegion>=0) CalcMyPos(K,true);

   K->flags=ImageList_LoadBitmap(ghThisInst,MAKEINTRESOURCE(1),16,0,RGB(255,0,255));
   CalcMiniScale(K);
   CalcRects(K);
   CreateGdiHandles();
   K->bInitialized=true;
   CalcMondalter();	// falls noch kein Wetterdialog da ist
   SetScale(K,42);
   HandleShift(K,-Config.kScrl.x,-Config.kScrl.y);
   kShowChange(K,0);	// löst WM_SIZE aus
   if (Wnd==hFirst) {
    if (Config.Checkmarks&0x80) PropRespawn();
    else PropAppendSysMenu();
   }
   kMiniSet(K,0,0);
   kWetterSet(K,0,0,0);
  }break;
  
  case WM_ACTIVATE: if (wParam) SetFocus(K->hMap); return 0;	// sonst kommen keine fokussierten Ereignisse an

  case WM_SIZE: {
   if (K->kShow&0x20) SendMessage(K->hToolbar,msg,wParam,lParam);
   if (K->kShow&0x40) SendMessage(K->hStatus,msg,wParam,lParam);
   if (K->hMap!=Wnd) {
    RECT rc;
    LoadClientRect(Wnd,&rc);
    MoveWindow(K->hMap,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,TRUE);
   }
  }nobreak;

  case WM_MOVE: {
   if ((K->kMini&3)==2) MiniParentChange(K);
  }break;

  case WM_INITMENU: {
   PrepareMenu(K,(HMENU)wParam);
  }break;

  case WM_FUNKRECV: switch ((BYTE)wParam) {
   case 16: {
    DWORD d=IndexToRegion((DWORD)lParam);
    if (((K->kWetter>>4)&3)!=HIBYTE(HIWORD(d))) return 0;	// falscher Tag
    switch (K->kWetter&15) {
     case 1:
     case 3:
     case 7: break;	// Tag & Nacht piepsen
     case 2:
     case 5: if (LOBYTE(LOWORD(d))) return 0; break;	// Nachtinfo? Nicht piepsen!
     case 4:
     case 6: if (LOBYTE(LOWORD(d))) break; return 0;	// Taginfo? Nicht piepsen!
     default: return 0;	// alles andere: nicht piepsen
    }
    InvalSymbChange(K,LOBYTE(HIWORD(d)));
    // Jetzt könnte man noch gucken, ob das Symbol im Kartenausschnitt ist ...
    // Und wie könnte man das neue Item animieren?
   }goto flash;
   case 17: {
    InvalSymbChange(K,-1);	// alle Wettersymbole ändern sich!
    flash:
    if (K->kWetter&0x40) {
     FLASHWINFO fwi={sizeof(fwi),Wnd,FLASHW_ALL,3};
     FlashWindowEx(&fwi);	// Bringt unter W2k keine optische Verbesseung!
//     FlashWindow(Wnd,TRUE);
     SetTimer(Wnd,0x40,5000,NULL);
     if (Wnd==hFirst) {
      MessageBeep(MB_ICONINFORMATION);
     }
    }
   }break;	// neu zeichnen (fällt das auf?)
   case 18: if ((char)lParam!=Config.Region) {	// Config.Region geändert: Bild aktualisieren, lParam = alter Wert
    InvalIndex(K,(int)lParam);
    InvalIndex(K,Config.Region);
    InvalSymbChange(K,90);
   }break;
   case 19: if (lParam!=1) {	// Config.uPos oder Vorzeichen von Config.uRegion geändert
    deleteMyPos(K);
    if (Config.uRegion>=0) CalcMyPos(K,true), CalcRects1(K,K->map[90]);
    InvalIndex(K,90);
    InvalSymbChange(K,90);
   }break;
  }break;

  case WM_MyPosStaat: return K->MyPosStaat;

  case WM_FocusRegion: FocusRegion(K,(int)wParam); break;

//  case WM_TIMER: KillTimer(Wnd,wParam); switch (wParam) {
//   case 0x40: FlashWindow(Wnd,FALSE); break;
//  }break;

  case WM_DRAWITEM: if (wParam==11) {	// hier: Statuszeile
   DRAWITEMSTRUCT*dis=(DRAWITEMSTRUCT*)lParam;
   int h=dis->rcItem.bottom-dis->rcItem.top;		// h = 16 (W2k), = 19 (XP; W2k Chemnitz)
   int m=(dis->rcItem.bottom+dis->rcItem.top-1)>>1;	// m = top+8 (W2k), = top+9 (XP: echte Mitte)
   int texttop,bmtop;
   SIZE textsize;			// cy = 13 (W2k,XP)
   GetTextExtentPoint32(dis->hDC,KARTECLASSNAME,1,&textsize);	// anscheinend für XP-Look so erforderlich
   texttop=m-(textsize.cy>>1);
   bmtop=m-7;
   SetBkMode(dis->hDC,TRANSPARENT);	// (undokumentiert wie Windows die übrigen Strings positioniert!)
   switch (dis->itemID) {
    case 2: if (K->HoverStaat>=0) {	// Land mit Flagge
     TCHAR s[64];
     int l=LoadString(ghInstance,352+K->HoverStaat,s,elemof(s));
     ImageList_Draw(K->flags,K->HoverStaat,dis->hDC,dis->rcItem.left+1,bmtop+1,0);
     ExtTextOut(dis->hDC,dis->rcItem.left+20,texttop,ETO_CLIPPED,&dis->rcItem,s,l,NULL);
    }break;
    case 4: {				// Warnung mit Achtungssymbol
     DrawIconEx(dis->hDC,dis->rcItem.left,bmtop,LoadIcon(0,IDI_EXCLAMATION),16,16,0,0,DI_NORMAL);
     ExtTextOut(dis->hDC,dis->rcItem.left+20,texttop,ETO_CLIPPED,&dis->rcItem,
       K->strStatus4,lstrlen(K->strStatus4),NULL);
    }break;
   }
  }break;

  case WM_CONTEXTMENU: {
   HMENU m=CreateMenu();
   HMENU sm=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
   if (K->kShow&0x10) {	// bei sichtbarer Menüzeile Kontextmenü ausdünnen
    DeleteMenu(sm,0,MF_BYPOSITION);	// Kein "Ansicht"
    DeleteMenu(sm,0,MF_BYPOSITION);	// Kein "Miniatur"
    DeleteMenu(sm,1,MF_BYPOSITION);	// Keine "Skalierung"
    DeleteMenu(sm,3,MF_BYPOSITION);	// Keine "Hilfe"
   }
   PrepareMenu(K,sm);
   AddBitmaps(GetSubMenu(sm,1),false);
   EnableMenuItem(sm,0x81,Config.uRegion>=0&&K->HoverIndex>=0&&K->HoverIndex!=Config.uRegion?MF_ENABLED:MF_GRAYED);	// Eigener Position Vorhersageregion zuordnen
   EnableMenuItem(sm,0x82,Config.uRegion>=0?MF_ENABLED:MF_GRAYED);	// Eigene Position löschen
   InsertMenu(m,0,MF_BYPOSITION|MF_POPUP,(UINT)sm,NULL);	// sonst geht's nicht! Warum auch immer.
   TrackPopupMenu(sm,TPM_RIGHTBUTTON,
     GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),0,Wnd,NULL);
   DestroyMenu(m);
   DelBitmaps(false);
  }break;
  
  case WM_SYSCOLORCHANGE: if (K->kShow&0x10) {	// schließt WM_THEMECHANGED ein
   BitmapUsage=-BitmapUsage;
   DelBitmaps(true);	// <true> ist eigentlich nur für das erste informierte Kartenfenster richtig
   AddBitmaps(GetSubMenu(GetMenu(Wnd),4),true);
  }break;

  case WM_COMMAND: switch (wParam&0xF0) {
   case 0x10: kShowChange(K,(BYTE)(1<<(wParam&7))); break;	// Menüzeile, Statuszeile usw.
   case 0x20: switch (wParam&0x0C) {
    case 0: kMiniSet(K,(BYTE)wParam,0x03); break;
    case 4: kMiniSet(K,(BYTE)(wParam<<2),0x0C); break;
    case 8: {
     char b=K->kMini;
     switch (wParam&3) {
      case 0: kMiniSet(K,(char)(b^0x10),-1); break;
      case 2: kMiniSet(K,(char)(b+0x20),-1); break;
      case 3: kMiniSet(K,(char)(b-0x20),-1); break;
     }
    }break;
   }break;
   case 0x30: kVisChange(K,(BYTE)(1<<(wParam&7))); break;
   case 0x40: switch (wParam&15) {
    case 0: SetScale(K,SB_PAGELEFT); break;
    case 1: SetScale(K,SB_LINELEFT); break;
    case 2: SetScale(K,SB_LINERIGHT); break;
    case 3: SetScale(K,SB_PAGERIGHT); break;
    case 4: SetScale(K,100); break;	// 100%
    case 5: SetScale(K,66); break;	// einpassen
   }break;
   case 0x50: switch (wParam&0x0C) {
    case 0: switch (wParam&3) {
     case 0:
     case 1: {
      int kode = -(K->kAutoScale^1<<(wParam&15));
      if (!kode) kode=42;
      SetScale(K,kode);
     }break;
     case 2: kAutoScaleSet(K,0,0,0x04); break;
     case 3: kAutoScaleSet(K,0,0,0x08); break;
    }break;
    case 0x08: kAutoScaleSet(K,(BYTE)(wParam<<4),0x30,0); break;
    case 0x0C: kAutoScaleSet(K,(BYTE)(wParam<<6),0xC0,0); break;
   }break;
   case 0x60: kWetterSet(K,(BYTE)wParam,0x0F,0); break;
   case 0x70: if (wParam&4) {
    kWetterSet(K,0,0,(BYTE)(0x40<<(wParam&1)));
   }else{
    kWetterSet(K,(BYTE)((wParam&3)<<4),0x30,0);
   }break;
   case 0x80: switch (wParam&15) {	// nur vom Kontextmenü
    case 0: {	// Eigene Position hier setzen
     int k;
     PointF geo={cp2bpf(K,K->MousePos.x,SB_HORZ),cp2bpf(K,K->MousePos.y,SB_VERT)};
     geo=MapMercator(K->kInfo->kUnmap(geo));	// Mercator-Pixel
     Config.uPos.x=(short)lrint(geo.X);
     Config.uPos.y=(short)lrint(geo.Y);
     k=K->HoverIndex;
     if (k<0) k=FindNearestCity(K,
       cp2bp(K,K->MousePos.x,SB_HORZ),
       cp2bp(K,K->MousePos.y,SB_VERT));	// sollte >=0 liefern
     Config.uRegion=(char)k;
     CalcMyPos(K,false);	// nur MyPosStaat ermitteln
     InfoPropSheet(19,0);	// Alle Karten (auch die eigene) müssen aktualisiert werden
    }break;
    case 1: {	// Diese Vorhersageregion der eigenen Position zuordnen
     int k=SearchHoverIndex(K);
     if (k>=0) Config.uRegion=k;
     InfoPropSheet(19,1);	// "Wetter"-Dialog aktualisieren
    }break;
    case 2: {	// Eigene Position löschen
     if (Config.Region==90) Config.Region=Config.uRegion;	// ggf. ändern!
     Config.uRegion=-1;
     InfoPropSheet(19,2);	// Alle Karten müssen aktualisiert werden
    }break;
   }break;
   case 0x90: {
    if (ChmHelp) HtmlHelp(Wnd,HelpName,HH_HELP_CONTEXT,wParam);
    else WinHelp(Wnd,HelpName,HELP_CONTEXT,wParam);
   }break;
  }break;

  case WM_NOTIFY: {
   LPNMHDR hdr=(LPNMHDR)lParam;
   if (hdr->idFrom>=0x60) switch (hdr->code) {	// Toolbar
    case TTN_GETDISPINFO: {
     LPNMTTDISPINFO di=(void*)lParam;
     HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
     if (0x70<=hdr->idFrom && hdr->idFrom<0x74) {
      TagesName((int)hdr->idFrom-0x70,K->strTip,elemof(K->strTip),0);
     }else{
      GetMenuString(m,(UINT)hdr->idFrom,K->strTip,elemof(K->strTip),0);
      StripAmpersand(K->strTip);
     }
     DestroyMenu(m);
     di->lpszText=K->strTip;
    }break;
   }else switch (hdr->code) {
    case TTN_SHOW: return 0;
    case TTN_GETDISPINFO: {	// == TTN_NEEDTEXT, für fliegenden Tooltip
     PTSTR a=K->strTip,e=a+elemof(K->strTip)-1;	// -1 = Platz für '\n'
     LPNMTTDISPINFO di=(void*)lParam;
     int l=0;
     di->lpszText=a;
     a+=MkGeoStr(K,a,(int)(e-a));
     if (K->HoverStaat>=0) {	// Ländergrenzen
      TCHAR t[256], s[64];
      LoadString(ghInstance,352+K->HoverStaat,s,elemof(s));
      LoadString(ghInstance,67,t,elemof(t));	// "Staat: %d" (wie in Statuszeile)
      *a++='\n';
      a+=wnsprintf(a,(int)(e-a),t,s);
     }
     if (K->HoverIndex>=0) {	// Vorhersageregion
      TCHAR t[256], s[128], *stadt;
      s[LoadString(ghInstance,256+K->HoverIndex,s,elemof(s)-1)+1]=0;	// doppelt terminieren
      stadt=s+lstrlen(s)+1;
      LoadString(ghInstance,70,t,elemof(t));	// Kombinations-String (4 Teile)
      *a++='\n';
      a+=wnsprintf(a,(int)(e-a),t,s);
      if (*stadt) {
       *a++='\n';
       a+=wnsprintf(a,(int)(e-a),
         GetStr(t,K->map[K->HoverIndex]->CitySize>3 ? 1/*Stadt*/ : 2/*Ort*/),stadt);
      }
      if (K->HoverIndex<90) {
       U64 v;
       LoadString(ghInstance,352+Flaggen[K->HoverIndex],s,elemof(s));
       *a++=' ';
       a+=wnsprintf(a,(int)(e-a),GetStr(t,3),s);
       v=GetTagWetter(K->kWetter,K->HoverIndex);
       if ((BYTE)0xE6>>(K->kWetter&7)&1) a+=SchwerwetterText(a,(int)(e-a),false,v,false);
       if ((BYTE)0xF8>>(K->kWetter&7)&1) a+=SchwerwetterText(a,(int)(e-a),true,v,false);
      }		// TODO: Tag/Nacht-Unterscheidung wenn beides
     }
    }break;
   }
  }break;

  case WM_CLOSE: {
   if (Wnd==hFirst && PropWnd) {
    if (Config.Checkmarks&0x80) {
     HWND w=hFirst;	// temporär verschwinden lassen
     hFirst=0;
     PropRespawn();	// ohne Owner (wieder) erzeugen
     hFirst=w;		// nachher setzen
    }else{
     HMENU m=GetSystemMenu(PropWnd,FALSE);	// Menüpunkt entfernen
     DeleteMenu(m,0x10,0);	// bRevert=TRUE geht nicht! (Das generiert das „lange“ Systemmenü)
    }
   }
  }break;

  case WM_DESTROY: {
   int i;
   if (Wnd==hFirst) {
    WINDOWPLACEMENT wp;
    wp.length=sizeof(wp);
    GetWindowPlacement(Wnd,&wp);
    Config.kShow=Config.kShow&0xFC|wp.showCmd&3;	// Ob das klappt?
    if ((wp.showCmd&3)==1) {	// nur bei "normal" Position speichern
     Config.kPos.left  =(short)wp.rcNormalPosition.left;
     Config.kPos.top   =(short)wp.rcNormalPosition.top;
     Config.kPos.right =(short)wp.rcNormalPosition.right;
     Config.kPos.bottom=(short)wp.rcNormalPosition.bottom;
    }
    Config.kScrl.x=(short)K->ScrollInfo[SB_HORZ].nPos;
    Config.kScrl.y=(short)K->ScrollInfo[SB_VERT].nPos;
    hFirst=0;
   }
   if (K->pPicture) {
    if (K->bPictureHBM) DeleteBitmap(K->hPicture);
    else (*(IPictureVtbl**)K->pPicture)->Release(K->pPicture);
   }
   DeleteGdiHandles();
   if (K->MiniBmp) DeleteBitmap(K->MiniBmp);
   for (i=0; i<NUMSTAAT; i++) if (K->staat[i]) DeleteRgn(K->staat[i]);
   if (GetMenu(Wnd)) DelBitmaps(false);
   ImageList_Destroy(K->flags);
  }break;

  case WM_NCDESTROY: {
   LocalFree(K);
  }return 0;
 }
/*** (D) Nachrichtenbehandlung für das Karten-Fenster, egal ob Child oder Top-Level ***/
 if (Wnd==K->hMap) switch (msg) {
  case WM_CREATE: {
   GetCursorPos(&K->MousePos);
   ScreenToClient(Wnd,&K->MousePos);
  }break;
  case WM_HSCROLL: HandleHScroll(K,(char)wParam); break;
  case WM_VSCROLL: HandleVScroll(K,(char)wParam); break;
  case WM_SIZE: {
   if (!K->bInitialized) break;		// noch nicht initialisiert
   if (++K->Recursion<=2) {
    LoadClientRect(Wnd,&K->ScrollRect);
    if (K->kAutoScale&3) SetScale(K,-(K->kAutoScale&3));
    else AdjustScrollRange(K);	// kann erneut WM_SIZE auslösen!
   }
   K->Recursion--;
   if (!K->Recursion && K->kMini&3) {
    MiniParentChange(K);	// Mini-Fenster ggf. repositionieren
    ShowMini(K);
    InvalidateRect(K->hMini,NULL,FALSE);
   }
  }break;
  
  // Mausereignisse (ggf. mit Capture)
  case WM_LBUTTONDOWN: {	// linke Maustaste wählt permanente Vorhersageregion aus
   K->MousePos.x=GET_X_LPARAM(lParam);	// Hier immer nachführen,
   K->MousePos.y=GET_Y_LPARAM(lParam);	// sonst unerwartete Schiebeoperation beim Schließen von Menüs
   K->bShifted=false;
  }break;
  case WM_LBUTTONDBLCLK: {
   if (!K->bShifted) PropShowWetter();
  }break;
  case WM_LBUTTONUP: if (!K->bShifted) {
#ifdef POLYEDIT
   savePoint(K);
#else
   SetRegion((char)SearchHoverIndex(K));
#endif
  }break;
  case WM_MOUSEMOVE: {
   TRACKMOUSEEVENT tme={sizeof(tme),TME_LEAVE,Wnd,100};
   TrackMouseEvent(&tme);
// Nach WM_MOUSEACTIVATE und WM_LBUTTONDOWN kommt immer ein WM_MOUSEMOVE ohne tatsächlichen Versatz
   if (wParam&MK_LBUTTON
   && HandleShift(K,GET_X_LPARAM(lParam)-K->MousePos.x,GET_Y_LPARAM(lParam)-K->MousePos.y))
     K->bShifted=true;
   K->MousePos.x=GET_X_LPARAM(lParam);	// immer nachführen
   K->MousePos.y=GET_Y_LPARAM(lParam);
   MousePosChanged(K);
   if (K->kShow&0x80) {
    DWORD pt=ClientToScreenS(Wnd,(DWORD)lParam)+0x180018;
    SendMessage(K->hTip,TTM_TRACKPOSITION,0,pt);
    TrackActivate(K,TRUE);
   }
  }break;
  case WM_MOUSELEAVE: {
   if (K->kShow&0x40) SendMessage(K->hStatus,SB_SETTEXT,1,0);	// StatusGeo: Kein Text
   SetHoverStaat(K,-1);
   SetHoverIndex(K,-1);
   if (K->kShow&0x80) TrackActivate(K,FALSE);
  }break;
  
  case WM_ERASEBKGND: {
   LoadClientRect(Wnd,&K->ScrollRect);	// WM_SIZE kommt _nach_ WM_ERASEBKGND (Win7-64)
   EraseOutside(K,(HDC)wParam); 
  }return 1;
  case WM_PRINTCLIENT: {
   DrawAll(K,(HDC)wParam,&K->ScrollRect);
  }return 0;
  case WM_PAINT: {
   PAINTSTRUCT ps;
   BeginPaint(Wnd,&ps);
   DrawAll(K,ps.hdc,&ps.rcPaint);
   EndPaint(Wnd,&ps);
  }return 0;

  // Fokussierte Ereignisse:
  case WM_MOUSEWHEEL: {
   i=(short)HIWORD(wParam);	// hier: WHEEL_DELTA nicht auswerten oder akkumulieren
   SetScale(K,i>=0?SB_LINERIGHT:SB_LINELEFT);
  }break;

  case WM_KEYDOWN: switch ((BYTE)wParam) {
   case VK_LEFT: HandleHScroll(K,SB_LINELEFT); break;
   case VK_RIGHT: HandleHScroll(K,SB_LINERIGHT); break;
   case VK_UP: HandleVScroll(K,SB_LINELEFT); break;
   case VK_DOWN: HandleVScroll(K,SB_LINERIGHT); break;
   case VK_PRIOR: HandleVScroll(K,SB_PAGERIGHT); break;
   case VK_NEXT: HandleVScroll(K,SB_PAGELEFT); break;
   case VK_HOME: HandleHScroll(K,SB_PAGERIGHT); break;
   case VK_END: HandleHScroll(K,SB_PAGELEFT); break;
   case VK_TAB: HandleTab(K,0); break;
   case VK_DELETE: if (Config.uRegion>=0) SendMessage(K->hMain,WM_COMMAND,0x82,0); break;
   case VK_RETURN: if (K->HoverIndex==Config.Region || K->HoverIndex<0) {
    PropShowWetter();
   }else{
    SetRegion(K->HoverIndex);
   }break;
#ifdef POLYEDIT
   case VK_BACK: saveUndo(K); break;
#endif
  }break;

  case WM_CHAR: if (IsCharAlpha((TCHAR)wParam)) HandleTab(K,(TCHAR)wParam);
  else{
   switch ((int)wParam) {
    case ' ': {	// Wetteranzeige durchschalten
     BYTE b=K->kWetter&0x3F;
     BYTE start=b;
     char dir=GetKeyState(VK_SHIFT)<0?-1:1;	// UMSCH+Leertaste = rückwärts
     for(;;){
      b+=dir;
      b&=0x3F;
      if (AnySymbol(b)) break;	// Nicht piepsen wenn genau eine Seite mit Symbolen existiert
      if (b==start) {MessageBeep(MB_ICONSTOP); break;}
     }
     if (b!=start) kWetterSet(K,b,0x3F,0);
    }break;
    case '1':
    case '2':
    case '3':
    case '4': kWetterSet(K,(BYTE)((wParam-'1')<<4),0x30,0); break;
    case '5':
    case '6':
    case '7':
    case '8':
    case '9': kWetterSet(K,(BYTE)(wParam-'5'+1),0x0F,0); break;
    case '0': kWetterSet(K,0,0x0F,0); break;
    case '#': kWetterSet(K,6,0x0F,0); break;
    case '^': kWetterSet(K,7,0x0F,0); break;
    case '+': SetScale(K,SB_LINERIGHT); break;	// ACDSee-kompatibel
    case '-': SetScale(K,SB_LINELEFT); break;
    case '/': SetScale(K,100); break;		// 100 = Magic für 100%
    case '*': SetScale(K,66); break;		// 66 = Magic für "an Fenstergröße anpassen"
#ifdef POLYEDIT
    case 27: saveEnd(K); break;
#endif
   }
   if ((int)wParam==sDecimal[0]) kWetterSet(K,6,0x0F,0); break;
   if ((int)wParam==(sDecimal[0]^('.'^','))) kWetterSet(K,7,0x0F,0); break;
  }break;
 }
 return DefWindowProc(Wnd,msg,wParam,lParam);
}

/******************************
 * Initialisierung und Aufruf *
 ******************************/

void RegisterKarte() {
 WNDCLASSEX wc={
   sizeof(wc),
   CS_DBLCLKS,
   MainWndProc,
   0,
   sizeof(karte*),
   ghThisInst,
   LoadIcon(ghThisInst,MAKEINTRESOURCE(100)),
   LoadCursor(0,IDC_ARROW),
   0,
   NULL,
   KARTECLASSNAME};
 RegisterClassEx(&wc);
#ifndef UNICODE
 {char s[10];		// Hintertür für manche Win98
  NoAlpha=(bool)GetEnvironmentVariable("NoAlpha",s,elemof(s));
 }
#endif
}

HWND Karte() {
 TCHAR s[64],*p1,*p2;
 if (GetKeyState(VK_SHIFT)>=0 && hFirst) {
  if (IsIconic(hFirst)) ShowWindow(hFirst,SW_RESTORE);	// In diesem Fall klappt alles
  SetActiveWindow(hFirst);	// Trotzdem bekommt das Fenster keinen Fokus (Win2k)
  BringWindowToTop(hFirst);	// Immerhin kommt es hoch (Win2k)
  return hFirst;
 }
 LoadString(ghInstance,64,s,elemof(s));
 p1=GetStr(s,1)-1;
 p2=GetStr(s,2)-1;
 if (hFirst) *p1=' ';	// "[sekundär]" erscheinen lassen
 if (!IsUserAdmin()) {
  *p2=' ';		// "¶" erscheinen lassen - als Ersatz für ein Negativ-Smiley
  if (!hFirst) lstrcpy(p1,p2);	// vorkopieren
 }
 return CreateWindow(KARTECLASSNAME,s,WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_CLIPCHILDREN,
   CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
   0,0,ghThisInst,NULL);
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded