/*
 * This file is part of the Simutrans project under the Artistic License.
 * (see LICENSE.txt)
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "../simcolor.h"
#include "../simevent.h"
#include "../display/simgraph.h"
#include "../display/viewport.h"
#include "../tool/simmenu.h"
#include "../simskin.h"
#include "../sys/simsys.h"
#include "../simticker.h"
#include "simwin.h"
#include "../simintr.h"
#include "../simhalt.h"
#include "../world/simworld.h"
#include "../utils/simrandom.h"

#include "../dataobj/translator.h"
#include "../dataobj/environment.h"
#include "../dataobj/loadsave.h"
#include "../dataobj/tabfile.h"

#include "../descriptor/skin_desc.h"

#include "../obj/zeiger.h"


#include "../player/simplay.h"
#include "../tpl/inthashtable_tpl.h"
#include "../tpl/vector_tpl.h"
#include "../utils/simstring.h"
#include "../utils/cbuffer.h"

#include "map_frame.h"
#include "help_frame.h"
#include "messagebox.h"
#include "gui_frame.h"

// needed to restore/save them
#include "tool_selector.h"
#include "player_frame.h"
#include "money_frame.h"
#include "halt_info.h"
#include "convoi_detail.h"
#include "convoi_frame.h"
#include "convoi_info.h"
#include "line_management_gui.h"
#include "schedule_list.h"
#include "city_info.h"
#include "citylist_frame.h"
#include "message_frame.h"
#include "chat_frame.h"
#include "message_option.h"
#include "fabrik_info.h"
#include "themeselector.h"
#include "goods_frame.h"
#include "loadfont_frame.h"
#ifdef USE_FLUIDSYNTH_MIDI
#include "loadsoundfont_frame.h"
#endif
#include "scenario_info.h"
#include "depot_frame.h"
#include "depotlist_frame.h"
#include "halt_list_frame.h"
#include "vehiclelist_frame.h"
#include "curiositylist_frame.h"
#include "factorylist_frame.h"
#include "labellist_frame.h"
#include "display_settings.h"
#include "optionen.h"
#include "player_ranking_frame.h"

#include "../simversion.h"

class inthashtable_tpl<ptrdiff_t,scr_coord> old_win_pos;

// hash-table: magic number to windowsize
class inthashtable_tpl<ptrdiff_t, scr_size> saved_windowsizes;

// true if there is little space for the status messages
static bool status_show_compact = false;

// true if toolbar magic
bool inline is_toolbar(ptrdiff_t magic) {
	return magic >= magic_toolbar && magic < magic_script_error;
}


// I added a button to the map window to fix it's size to the best one.
// This struct is the flow back to the object of the refactoring.
class simwin_gadget_flags_t
{
public:
	simwin_gadget_flags_t(  ) : title(true), close( false ), help( false ), prev( false ), size( false ), next( false ), sticky( false ), gotopos( false ) { }

	bool title:1;
	bool close:1;
	bool help:1;
	bool prev:1;
	bool size:1;
	bool next:1;
	bool sticky:1;
	bool gotopos:1;
};

class simwin_t
{
public:
	scr_coord pos;          // Window position
	uint32 dauer;           // How long should the window stay open?
	uint8 wt;               // the flags for the window type
	ptrdiff_t magic_number; // either magic number or this pointer (which is unique too)
	gui_frame_t *gui;
	uint16 gadget_state;    // which buttons to hilite
	bool sticky;            // true if window is sticky
	bool rollup;
	bool dirty;

	simwin_gadget_flags_t flags; // See Above.

	simwin_t() : flags() {}

	bool operator== (const simwin_t &) const;
};

bool simwin_t::operator== (const simwin_t &other) const { return gui == other.gui; }


#define MAX_WIN (64)
static vector_tpl<simwin_t> wins(MAX_WIN);
static vector_tpl<simwin_t> kill_list(MAX_WIN);

static karte_t* wl = NULL; // Pointer to current world is set in win_set_world

static int top_win(int win, bool keep_state );
static void display_win(int win);


// tooltip data
static scr_coord_val tooltip_xpos = 0;
static scr_coord_val tooltip_ypos = 0;
static const char * tooltip_text = 0;
static std::string static_tooltip_text;

// For timed tooltip with initial delay and finite visible duration.
// Valid owners are required for timing. Invalid (NULL) owners disable timing.
static const void * tooltip_owner = 0;   // owner of the registered tooltip
static const void * tooltip_group = 0;   // group to which the owner belongs
static uint32 tooltip_register_time = 0; // time at which a tooltip is initially registered

static bool show_ticker=0;

/* if we are inside the event handler,
 * the window handler has gui pointer as value,
 * to defer destruction if this window
 */
static void *inside_event_handling = NULL;

// only this gui element can set a tooltip
static void *tooltip_element = NULL;

static bool destroy_framed_win(simwin_t *win);

//=========================================================================
// Helper Functions

#define REVERSE_GADGETS (!env_t::window_buttons_right)

/**
 * Display a window gadget
 */
static int display_gadget_box(sint8 code,
			      int const x, int const y,
			      PIXVAL lighter, PIXVAL darker,
			      bool const pushed)
{

	// If we have a skin, get gadget image data
	const image_t *img = NULL;
	if(  skinverwaltung_t::gadget  ) {
		// "x", "?", "=", "<<", ">>"
		img = skinverwaltung_t::gadget->get_image(code);
	}

	if(pushed) {
		// mark_rect_dirty_wc(x, y, D_GADGET_WIDTH, D_TITLEBAR_HEIGHT);
		gfx->draw_rect_clipped(x, y, D_GADGET_WIDTH, D_TITLEBAR_HEIGHT, lighter, false CLIP_NUM_DEFAULT);
	}

	// Do we have a gadget image?
	if(  img != NULL  ) {

		// Max Kielland: This center the gadget image and compensates for any left/top margins within the image to be backward compatible with older PAK sets.
		gfx->draw_img_aligned(img->imageid, scr_rect(x, y, D_GADGET_WIDTH, D_TITLEBAR_HEIGHT), ALIGN_CENTER_H | ALIGN_CENTER_V, false);

	}
	else {
		const char *gadget_text = "#";
		static const char *gadget_texts[5]={ "X", "?", "=", "<", ">" };

		if(  code <= SKIN_BUTTON_NEXT  ) {
			gadget_text = gadget_texts[code];
		}
		else if(  code == SKIN_GADGET_GOTOPOS  ) {
			gadget_text = "*";
		}
		else if(  code == SKIN_GADGET_NOTPINNED  ) {
			gadget_text = "s";
		}
		else if(  code == SKIN_GADGET_PINNED  ) {
			gadget_text = "S";
		}
		gfx->draw_text( x+4, y+4, gadget_text, ALIGN_LEFT, SYSCOL_TEXT, false );
	}

	int side = x+REVERSE_GADGETS*D_GADGET_WIDTH-1;
	gfx->draw_vline_clipped(side+1, y+1, D_TITLEBAR_HEIGHT-2, lighter, false CLIP_NUM_DEFAULT);
	gfx->draw_vline_clipped(side,   y+1, D_TITLEBAR_HEIGHT-2, darker,  false CLIP_NUM_DEFAULT);

	// return width of gadget
	return D_GADGET_WIDTH;
}


static int display_gadget_boxes(
	simwin_gadget_flags_t* flags,
	int x, int y,
	PIXVAL lighter,
	PIXVAL darker,
	uint16 gadget_state,
	bool sticky_pushed,
	bool goto_pushed
) {
	int width = 0;
	const int k=(REVERSE_GADGETS?1:-1);

	// Only the close and sticky gadget can be pushed.
	if(  flags->close  ) {
		width += k*display_gadget_box( SKIN_GADGET_CLOSE, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_CLOSE) );
	}
	if(  flags->size  ) {
		width += k*display_gadget_box( SKIN_GADGET_MINIMIZE, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_MINIMIZE) );
	}
	if(  flags->help  ) {
		width += k*display_gadget_box( SKIN_GADGET_HELP, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_HELP) );
	}
	if(  flags->prev  ) {
		width += k*display_gadget_box( SKIN_BUTTON_PREVIOUS, x + width, y, lighter, darker, gadget_state & (1<<SKIN_BUTTON_PREVIOUS) );
	}
	if(  flags->next  ) {
		width += k*display_gadget_box( SKIN_BUTTON_NEXT, x + width, y, lighter, darker, gadget_state & (1<<SKIN_BUTTON_NEXT) );
	}
	if(  flags->gotopos  ) {
		width += k*display_gadget_box( SKIN_GADGET_GOTOPOS, x + width, y, lighter, darker, goto_pushed  ||  (gadget_state & (1<<SKIN_GADGET_GOTOPOS)) );
	}
	if(  flags->sticky  ) {
		width += k*display_gadget_box( sticky_pushed ? SKIN_GADGET_PINNED : SKIN_GADGET_NOTPINNED, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_NOTPINNED) );
	}

	return abs( width );
}


static sint8 decode_gadget_boxes(simwin_gadget_flags_t const * const flags, int const x,int const px)
{
	int offset = px-x;
	const int w=(REVERSE_GADGETS?-D_GADGET_WIDTH:D_GADGET_WIDTH);

//DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","offset=%i, w=%i",offset, w );

	// Only the close gadget can be pushed.
	if( flags->close ) {
		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
//DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","close" );
			return SKIN_GADGET_CLOSE;
		}
		offset += w;
	}
	if( flags->size ) {
		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
//DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","size" );
			return SKIN_GADGET_MINIMIZE;
		}
		offset += w;
	}
	if( flags->help ) {
		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
//DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","help" );
			return SKIN_GADGET_HELP;
		}
		offset += w;
	}
	if( flags->prev ) {
		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
			return SKIN_BUTTON_PREVIOUS;
		}
		offset += w;
	}
	if( flags->next ) {
		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
			return SKIN_BUTTON_NEXT;
		}
		offset += w;
	}
	if( flags->gotopos ) {
		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
			return SKIN_GADGET_GOTOPOS;
		}
		offset += w;
	}
	if( flags->sticky ) {
		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
			return SKIN_GADGET_NOTPINNED;
		}
		offset += w;
	}
	return SKIN_GADGET_COUNT;
}


static void win_draw_window_title(const scr_coord pos, const scr_size size,
		const FLAGGED_PIXVAL title_color,
		const char * const text,
		const FLAGGED_PIXVAL text_color,
		const koord3d welt_pos,
		const uint16 gadget_state,
		const bool sticky,
		const bool goto_pushed,
		simwin_gadget_flags_t &flags )
{
	PUSH_CLIP_FIT(pos.x, pos.y, size.w, size.h);

	PIXVAL lighter = gfx->blend_colors(title_color, gfx->palette_lookup(COL_WHITE), 25);
	PIXVAL darker  = gfx->blend_colors(title_color, gfx->palette_lookup(COL_BLACK), 25);

	// fill title bar with color
	gfx->draw_rect_clipped(pos.x, pos.y, size.w, D_TITLEBAR_HEIGHT, title_color, true CLIP_NUM_DEFAULT);

	// border of title bar
	gfx->draw_rect_clipped( pos.x + 1, pos.y,                         size.w - 2, 1, lighter, false CLIP_NUM_DEFAULT); // top
	gfx->draw_rect_clipped( pos.x + 1, pos.y + D_TITLEBAR_HEIGHT - 1, size.w - 2, 1, darker,  false CLIP_NUM_DEFAULT); // bottom

	gfx->draw_vline_clipped( pos.x,              pos.y, D_TITLEBAR_HEIGHT, lighter, false CLIP_NUM_DEFAULT); // left
	gfx->draw_vline_clipped( pos.x + size.w - 1, pos.y, D_TITLEBAR_HEIGHT, darker,  false CLIP_NUM_DEFAULT); // right

	// Draw the gadgets and then move left and draw text.
	flags.gotopos = (welt_pos != koord3d::invalid);
	int width = display_gadget_boxes( &flags, pos.x+(REVERSE_GADGETS?0:size.w-D_GADGET_WIDTH), pos.y, lighter, darker, gadget_state, sticky, goto_pushed );
	int titlewidth = gfx->draw_text_clipped( pos.x + (REVERSE_GADGETS?width+4:4), pos.y+(D_TITLEBAR_HEIGHT-LINEASCENT)/2, text, ALIGN_LEFT, text_color, false );
	if(  flags.gotopos  ) {
		gfx->draw_text_clipped( pos.x + (REVERSE_GADGETS?width+4:4)+titlewidth+8, pos.y+(D_TITLEBAR_HEIGHT-LINEASCENT)/2, welt_pos.get_fullstr(), ALIGN_LEFT, text_color, false );
	}
	POP_CLIP();
}


//-------------------------------------------------------------------------

/**
 * Draw dragger widget
 */
static void win_draw_window_dragger(scr_coord pos, scr_size size)
{
	pos += size;
	if(  skinverwaltung_t::gadget  &&  skinverwaltung_t::gadget->get_image_id(SKIN_WINDOW_RESIZE)!=IMG_EMPTY  ) {
		const image_t *dragger = skinverwaltung_t::gadget->get_image(SKIN_WINDOW_RESIZE);
		gfx->draw_color_img( dragger->get_id(), pos.x-dragger->get_pic()->w, pos.y-dragger->get_pic()->h, 0, false, false CLIP_NUM_DEFAULT);
	}
	else {
		int dragger_size = min(D_DRAGGER_WIDTH, D_DRAGGER_HEIGHT);
		for(  int x=1;  x<dragger_size;  x++  ) {
			gfx->draw_rect_clipped( pos.x-x, pos.y-dragger_size+x, x, 1, gfx->palette_lookup((x & 1) ? COL_BLACK : MN_GREY4), true CLIP_NUM_DEFAULT);
		}
	}
}

// Functions to save & restore windowsize

ptrdiff_t guess_magic_number(simwin_t *win, ptrdiff_t magic = magic_none)
{
	if (win) {
		magic = win->magic_number;
	}
	// reduce player-wise magic numbers and magic numbers derived from handles
	const ptrdiff_t magic_pl[] = {
		magic_finances_t, magic_convoi_list, magic_convoi_list_filter, magic_line_list, magic_halt_list,
		magic_line_management_t, magic_ai_options_t, magic_ai_selector, magic_pwd_t, magic_jump, magic_headquarter, magic_headquarter + MAX_PLAYER_COUNT,
		magic_none,
		magic_convoi_info, magic_halt_info, magic_toolbar,
	};

	for (uint i=1; i<lengthof(magic_pl); i++) {
		if (magic_pl[i-1] == magic_none) {
			continue;
		}
		if (magic_pl[i-1] <= magic  &&  magic < magic_pl[i]) {
			return magic_pl[i-1];
		}
	}
	if (win == NULL) {
		return magic;
	}
	// try rdwr_id
	uint32 id = win->gui->get_rdwr_id();
	if (id != magic_reserved) {
		return id;
	}

	// anything outside the magic number's ranges will be ignored
	if (magic <= magic_reserved  ||  magic >= magic_max) {
		magic = magic_none;
	}
	return magic;
}


static void save_windowsize(simwin_t *win)
{
	ptrdiff_t magic = guess_magic_number(win);
	if (magic != magic_none) {
		saved_windowsizes.set(magic, win->gui->get_windowsize() );
	}
}


static scr_size get_stored_windowsize(simwin_t *win)
{
	ptrdiff_t magic = guess_magic_number(win);
	if (magic != magic_none) {
		return saved_windowsizes.get(magic);
	}
	return scr_size();
}


void rdwr_win_settings(loadsave_t *file)
{
	if (file->is_loading()) {
		saved_windowsizes.clear();
		ptrdiff_t magic;
		do {
			sint64 rd_magic;
			file->rdwr_longlong(rd_magic);
			magic = (ptrdiff_t)rd_magic;
			if (magic != magic_none) {
				scr_size s;
				file->rdwr_long(s.w);
				file->rdwr_long(s.h);
				// ignore stuff that will not be used anymore
				if (magic == guess_magic_number(NULL, magic)) {
					saved_windowsizes.put(magic, s);
				}
			}
		} while (magic != magic_none);
	}
	else {
		for(auto it : saved_windowsizes) {
			sint64 m = it.key;
			file->rdwr_longlong(m);
			file->rdwr_long(it.value.w);
			file->rdwr_long(it.value.h);
		}
		sint64 m = magic_none;
		file->rdwr_longlong(m);
	}
}
//=========================================================================



// returns the window (if open) otherwise zero
gui_frame_t *win_get_magic(ptrdiff_t magic)
{
	if(  magic!=-1  &&  magic!=0  ) {
		// there is at most one window with a positive magic number
		for(simwin_t const& i : wins ) {
			if(  i.magic_number == magic  ) {
				// if 'special' magic number, return it
				return i.gui;
			}
		}
	}
	return NULL;
}


// sets the magic of a gui_frame_t (needed during reload of windows)
bool win_set_magic( gui_frame_t *gui, ptrdiff_t magic )
{
	if(  magic!=-1  &&  magic!=0  ) {
		// there is at most one window with a positive magic number
		for(simwin_t &i : wins ) {
			if(  i.gui == gui  ) {
				i.magic_number = magic;
				return true;
			}
		}
	}
	return false;
}


/**
 * Returns top window
 */
gui_frame_t *win_get_top()
{
	return wins.empty() ? 0 : wins.back().gui;
}


/**
 * returns the focused component of the top window
 */
gui_component_t *win_get_focus()
{
	return wins.empty() ? 0 : wins.back().gui->get_focus();
}


bool win_is_textinput()
{
	if(  gui_component_t *comp = win_get_focus()  ) {
		return dynamic_cast<gui_textinput_t *>(comp) || dynamic_cast<gui_combobox_t *>(comp)  ||  dynamic_cast<gui_numberinput_t *>(comp);
	}
	return false;
}


uint32 win_get_open_count()
{
	return wins.get_count();
}


gui_frame_t* win_get_index(uint32 i)
{
	if (i < wins.get_count()) {
		return wins[i].gui;
	}
	return NULL;
}


// brings a window to front, if open
bool top_win( const gui_frame_t *gui, bool keep_rollup )
{
	for(  uint i=0;  i<wins.get_count()-1;  i++  ) {
		if(wins[i].gui==gui) {
			top_win(i,keep_rollup);
			return true;
		}
	}
	// not open
	return false;
}


/**
 * Checks if a window is a top level window
 */
bool win_is_top(const gui_frame_t *ig)
{
	return !wins.empty() && wins.back().gui == ig;
}


// window functions


// save/restore all dialogues
void rdwr_all_win(loadsave_t *file)
{
	if(  file->is_version_atleast(120, 8)  ) {
		if(  file->is_saving()  ) {
			for(simwin_t & i : wins) {
				uint32 id = i.gui->get_rdwr_id();
				if(  id!=magic_reserved  ) {
					file->rdwr_long( id );
					i.pos.rdwr(file);
					file->rdwr_byte(i.wt);
					file->rdwr_bool(i.sticky);
					file->rdwr_bool(i.rollup);
					i.gui->rdwr(file);
				}
			}
			uint32 end = magic_none;
			file->rdwr_long( end );
		}
		else {
			// restore windows
			while(1) {
				uint32 id;
				file->rdwr_long(id);
				// create the matching
				gui_frame_t *w = NULL;
				switch(magic_numbers(id)) {

					// end of dialogues
					case magic_none: return;

					// actual dialogues to restore
					case magic_themes:         w = new themeselector_t(); break;
					case magic_reliefmap:      w = new map_frame_t(); break;
					case magic_ki_kontroll_t:  w = new ki_kontroll_t(); break;
					case magic_line_schedule_rdwr_dummy: w = new line_management_gui_t(); break;
					case magic_city_info_t:    w = new city_info_t(); break;
					case magic_messageframe:   w = new message_frame_t(); break;
					case magic_message_options: w = new message_option_t(); break;
					case magic_chatframe:      w = new chat_frame_t(); break;
					case magic_factory_info:   w = new fabrik_info_t(); break;
					case magic_goodslist:      w = new goods_frame_t(); break;
					case magic_font:           w = new loadfont_frame_t(); break;
#ifdef USE_FLUIDSYNTH_MIDI
					case magic_soundfont:      w = new loadsoundfont_frame_t(); break;
#endif
					case magic_scenario_info:  w = new scenario_info_t(); break;
					case magic_depot:          w = new depot_frame_t(); break;
					case magic_convoi_list:    w = new convoi_frame_t(); break;
					case magic_depotlist:      w = new depotlist_frame_t(); break;
					case magic_vehiclelist:    w = new vehiclelist_frame_t(); break;
					case magic_halt_list:      w = new halt_list_frame_t(); break;
					case magic_citylist_frame_t: w = new citylist_frame_t(); break;
					case magic_curiositylist:  w = new curiositylist_frame_t(); break;
					case magic_factorylist:    w = new factorylist_frame_t(); break;
					case magic_labellist:      w = new labellist_frame_t(); break;
					case magic_color_gui_t:    w = new color_gui_t(); break;
					case magic_optionen_gui_t: w = new optionen_gui_t(); break;
					case magic_player_ranking: w = new player_ranking_frame_t(); break;

					default:
						if(  id>=magic_finances_t  &&  id<magic_finances_t+MAX_PLAYER_COUNT  ) {
							w = new money_frame_t( wl->get_player(id-magic_finances_t) );
						}
						else if(  id>=magic_line_management_t  &&  id<magic_line_management_t+MAX_PLAYER_COUNT  ) {
							w = new schedule_list_gui_t( wl->get_player(id-magic_line_management_t) );
						}
						else if(  id>=magic_toolbar  &&  id<magic_toolbar+256  ) {
							tool_t::toolbar_tool[id-magic_toolbar]->update(wl->get_active_player());
							w = tool_t::toolbar_tool[id-magic_toolbar]->get_tool_selector();
						}
						else if( id>=magic_convoi_info && id<magic_convoi_info+0x10000  ) {
							w = new convoi_info_t();
						}
						else if( id>=magic_halt_info  &&  id<magic_halt_info+0x10000  ) {
							w = new halt_info_t();
						}
						else {
							dbg->error( "rdwr_all_win()", "No idea how to restore magic 0x%X", id );
							return;
						}
				}
				/* sequence is now the same for all dialogues
				 * restore coordinates
				 * create window
				 * read state
				 * restore content
				 * restore state - gui_frame_t::rdwr() might create its own window ->> want to restore state to that window
				 */
				scr_coord p;
				p.rdwr(file);
				uint8 win_type;
				file->rdwr_byte( win_type );
				create_win( p, w, (wintype)win_type, id );
				bool sticky, rollup;
				file->rdwr_bool( sticky );
				file->rdwr_bool( rollup );
				// now load the window
				uint32 count = wins.get_count();
				w->rdwr( file );

				// restore sticky / rollup status
				// ensure that the new status is to currently loaded window
				if (wins.get_count() >= count) {
					wins.back().sticky = sticky;
					wins.back().rollup = rollup;
				}
			}
		}
	}
}



scr_rect win_get_max_window_area()
{
	const scr_size screen = gfx->get_screen_size();

	scr_coord other_pos((env_t::menupos == MENU_LEFT) * env_t::iconsize.w, (env_t::menupos == MENU_TOP) * env_t::iconsize.h + (env_t::menupos == MENU_BOTTOM) * win_get_statusbar_height());
	scr_size other_size{
		screen.w - other_pos.x - (env_t::menupos == MENU_RIGHT) * env_t::iconsize.w,
		screen.h - win_get_statusbar_height() - env_t::iconsize.h
	};

	if (show_ticker) {
		other_size.h -= TICKER_HEIGHT;
		if (env_t::menupos == MENU_BOTTOM) {
			other_pos.y += TICKER_HEIGHT;
		}
	}
	return scr_rect(other_pos, other_size);
}



/* tries to get a window on the screen
 * without titlebar hidden by any menubar/satus bar elements
 */
void win_clamp_xywh_position( gui_frame_t* gui, scr_coord &pos, scr_size wh, bool move_topleft )
{
	// reposition to keep on screen
	scr_rect screen = win_get_max_window_area();
	scr_coord other_pos  = screen.get_pos();
	scr_size  other_size = screen.get_size();

	if (pos.y < other_pos.y) {
		// y alsways visible
		pos.y = other_pos.y;
	}
	if (move_topleft) {
		if (pos.x < other_pos.x) {
			pos.x = other_pos.x;
		}
	}

	// should we move to be fully on screen?
	if (move_topleft) {
		if (pos.x + wh.w > screen.x + screen.w) {
			pos.x = screen.x + screen.w - wh.w;
		}
		if (pos.y + wh.h > screen.y + screen.h) {
			pos.y = screen.y + screen.h - wh.h;
		}
	}

	if (gui) {
		if (wh.w > other_size.w || wh.h > other_size.h) {
			// window still to big => maybe we can resize it to fit the screen
			scr_size minsize = gui->get_min_windowsize();
			if (minsize != wh) {
				bool resize = false;
				if (pos.x < other_pos.x  &&  minsize.w < other_size.w) {
					// too wide
					pos.x = other_pos.x;
					minsize.w = other_size.w;
					resize = true;
				}
				if (pos.y < other_pos.y  &&  minsize.h < other_size.h) {
					//too high
					pos.y = other_pos.y;
					minsize.h = other_size.h;
					resize = true;
				}
				if (resize) {
					gui->set_windowsize(minsize);
				}
			}
			else if (move_topleft) {
				// center on screen
				if (pos.x < other_pos.x) {
					pos.x = other_pos.x - (wh.w - other_size.w) / 2;
				}
				if (pos.y < other_pos.y) {
					pos.y = other_pos.y - (wh.h - other_size.h) / 2;
				}
			}
			// make sure some corner remains visible
			if (pos.x + wh.w < other_pos.x + D_MARGIN_LEFT) {
				pos.x = other_pos.x + D_MARGIN_LEFT - wh.w;
			}
			if (pos.x > other_pos.x + other_size.w - D_MARGIN_LEFT) {
				pos.x = other_pos.x + other_size.w - D_MARGIN_LEFT;
			}
			if (pos.y + D_TITLEBAR_HEIGHT > other_pos.y + other_size.h) {
				pos.y = other_pos.y + other_size.h - D_TITLEBAR_HEIGHT;
			}
		}
	}
}

int create_win(gui_frame_t* const gui, wintype const wt, ptrdiff_t const magic)
{
	return create_win({ -1, -1 }, gui, wt, magic);
}

int create_win(scr_coord pos, gui_frame_t *const gui, wintype const wt, ptrdiff_t const magic)
{
	assert(gui!=NULL  &&  magic!=0);

	if(  gui_frame_t *win = win_get_magic(magic)  ) {
		if(  env_t::second_open_closes_win  ) {
			destroy_win( win );
			if(  !( wt & w_do_not_delete )  ) {
				delete gui;
			}
		}
		else {
			top_win( win );
		}
		return -1;
	}

	if(  pos.x==-1  &&  pos.y==-1  &&  env_t::remember_window_positions  ) {
		// look for window in hash table
		if(  scr_coord *k = old_win_pos.access(magic)  ) {
			bool overlap = false;
			if (env_t::stack_toolbars && is_toolbar(magic)) {
				// make sure we do not overlap with other toolsbars
				for (uint32 i = 0; i < wins.get_count()  && !overlap; i++) {
					if (wins[i].wt && is_toolbar(wins[i].magic_number)) {
						overlap = k->y >= wins[i].pos.y && k->y < wins[i].pos.y + wins[i].gui->get_windowsize().h;
					}
				}
			}
			if(!overlap) {
				pos = *k;
			}
		}
	}

	/* if there are too many handles (likely in large games)
	 * we search for any error/news message at the bottom of the stack and delete it
	 * => newer information might be more important ...
	 * This is a stopgap to avoid simutrans to freeze due to many error etc windows
	 */
	if(  wins.get_count()==MAX_WIN  ) {
		// we try to remove one of the lowest news windows (magic_none)
		for(  uint i=0;  i<MAX_WIN;  i++  ) {
			if(  wins[i].magic_number == magic_none  &&  dynamic_cast<news_window *>(wins[i].gui)!=NULL  ) {
				destroy_win( wins[i].gui );
				break;
			}
		}
	}

	if(  wins.get_count() < MAX_WIN  ) {

		if (!wins.empty()) {
			// mark old dirty
			const scr_size size = wins.back().gui->get_windowsize();
			gfx->mark_rect_dirty_wc( wins.back().pos.x - 1, wins.back().pos.y - 1, wins.back().pos.x + size.w + 2, wins.back().pos.y + size.h + 2 + D_TITLEBAR_HEIGHT ); // -1, +2 for env_t::window_frame_active
		}

		wins.append( simwin_t() );
		simwin_t& win = wins.back();

		// Make Sure Closes Aren't Forgotten.
		// Must Reset as the entries and thus flags are reused
		win.flags.close = true;
		win.flags.title = gui->has_title();
		win.flags.help = ( gui->get_help_filename() != NULL );
		win.flags.prev = gui->has_prev();
		win.flags.next = gui->has_next();
		win.flags.sticky = gui->has_sticky();
		win.gui = gui;

		// take care of time delete windows ...
		win.wt    = wt & w_time_delete ? w_info : wt;
		win.dauer = (wt&w_time_delete) ? MESG_WAIT : -1;
		win.magic_number = magic;
		win.gadget_state = 0;
		win.rollup = false;
		win.sticky = false;
		win.dirty = true;

		// Notify window to be shown
		assert(gui);
		event_t ev;

		ev.ev_class = INFOWIN;
		ev.ev_code = WIN_OPEN;
		ev.mouse_pos.x = 0;
		ev.mouse_pos.y = 0;
		ev.click_pos.x = 0;
		ev.click_pos.y = 0;
		ev.mouse_button_state = 0;

		void *old = inside_event_handling;
		inside_event_handling = gui;
		gui->infowin_event(&ev);
		inside_event_handling = old;

		// restore windowsize
		scr_size stored = get_stored_windowsize(&win);

		if (stored == scr_size()) {
			// not stored, take current
			stored = gui->get_windowsize();
		}

		// use default width
		stored.clip_lefttop(scr_size(D_DEFAULT_WIDTH, D_DEFAULT_HEIGHT));
		// clip to display size
		const scr_size screen = gfx->get_screen_size();
		stored.clip_rightbottom( scr_size(screen.w, screen.h - env_t::iconsize.h - win_get_statusbar_height() ) );

		if (stored != gui->get_windowsize()) {
			// send tailored resize event
			scr_size delta = stored - gui->get_windowsize();
			event_t wev;
			wev.ev_class = WINDOW_RESIZE;
			wev.ev_code = 0;
			wev.mouse_pos.x = delta.w;
			wev.mouse_pos.y = delta.h;

			inside_event_handling = gui;
			gui->infowin_event(&wev);
			inside_event_handling = old;
		}

		if (pos.x == -1) {

			if (env_t::stack_toolbars  &&  is_toolbar(magic)) {
				// try to keep the toolbar next to other toolbars
				scr_coord_val direction = 1;
				pos.x = 0;
				pos.y = 0;
				if (env_t::menupos == MENU_BOTTOM) {
					direction = -1;
					pos.y = gfx->get_screen_size().h - gui->get_windowsize().h - env_t::iconsize.h;
				}
				// now we just open below (above), not caring for holes
				for (uint32 i=0;  i<wins.get_count()-1;  i++) {
					if (wins[i].wt  &&  is_toolbar(wins[i].magic_number)) {
						// toolbar
						if (direction > 0) {
							pos.y = max(pos.y, wins[i].pos.y + wins[i].gui->get_windowsize().h);
						}
						else {
							pos.y = min(pos.y, wins[i].pos.y - gui->get_windowsize().h - D_TITLEBAR_HEIGHT);
						}
					}
				}
				if (env_t::menupos == MENU_RIGHT) {
					// right aligned
					pos.x = gfx->get_screen_size().w - gui->get_windowsize().w - env_t::iconsize.w;
				}
				win_clamp_xywh_position(gui, pos, gui->get_windowsize(), true);
			}
			else {
				// try to go next to mouse bar
				pos.x = get_mouse_pos().x - gui->get_windowsize().w / 2;
				pos.y = get_mouse_pos().y - gui->get_windowsize().h - gfx->get_tile_raster_width()/4;
			}
		}

		// make sure window is on screen
		win_clamp_xywh_position(gui, pos, gui->get_windowsize(), true);

		win.pos = pos;
		win.dirty = true;
		return wins.get_count();
	}
	else {
		if(  !( wt & w_do_not_delete )  ) {
			delete gui;
		}
		return -1;
	}
}


static int notify_top_win()
{
	// mark new dirty
	scr_size size = wins.back().gui->get_windowsize();
	gfx->mark_rect_dirty_wc( wins.back().pos.x - 1, wins.back().pos.y - 1, wins.back().pos.x + size.w + 2, wins.back().pos.y + size.h + 2 ); // -1, +2 for env_t::window_frame_active

	event_t ev;

	ev.ev_class = INFOWIN;
	ev.ev_code = WIN_TOP;
	ev.mouse_pos.x = 0;
	ev.mouse_pos.y = 0;
	ev.click_pos.x = 0;
	ev.click_pos.y = 0;
	ev.mouse_button_state = 0;

	void *old = inside_event_handling;
	inside_event_handling = wins.back().gui;
	wins.back().gui->infowin_event(&ev);
	inside_event_handling = old;

	return wins.get_count()-1;
}


int top_win(int win, bool keep_state )
{
	if(  (uint32)win==wins.get_count()-1  ) {
		return win;
	} // already topped


	// mark old dirty
	scr_size size = wins.back().gui->get_windowsize();
	gfx->mark_rect_dirty_wc( wins.back().pos.x - 1, wins.back().pos.y - 1, wins.back().pos.x + size.w + 2, wins.back().pos.y + size.h + 2 ); // -1, +2 for env_t::window_frame_active
	wins.back().dirty = true;

	simwin_t tmp = wins[win];
	wins.remove_at(win);
	if(  !keep_state  ) {
		tmp.rollup = false; // make visible when topping
	}
	wins.append(tmp);

	return notify_top_win();
}


static void save_win_position(const simwin_t &win)
{
	if(  win.magic_number < magic_max  ) {
		if(  scr_coord *k = old_win_pos.access(win.magic_number)  ) {
			*k = win.pos;
		}
		else {
			old_win_pos.put( win.magic_number, win.pos );
		}
	}
}


/* sometimes a window cannot destroyed while it is still handled;
 * in those cases it will added to kill list and it is only destructed
 * by this function
 */
static void process_kill_list()
{
	bool needs_top = !kill_list.empty();
	for(simwin_t & i : kill_list) {
		if (inside_event_handling != i.gui) {
			save_win_position(i);
			destroy_framed_win(&i); // we call this first, otherwise the focus may not be recognized
			wins.remove(i);
		}
	}
	kill_list.clear();
	// top message always after destruction
	if(  needs_top  &&  wins.get_count()>1  ) {
		notify_top_win();
	}
}


/**
 * Destroy a framed window
 */
static bool destroy_framed_win(simwin_t *wins)
{
	bool r = true;

	// mark dirty
	const scr_size size = wins->gui->get_windowsize();
	gfx->mark_rect_dirty_wc( wins->pos.x - 1, wins->pos.y - 1, wins->pos.x + size.w + 2, wins->pos.y + size.h + 2 ); // -1, +2 for env_t::window_frame_active

	// save windowsize for later
	save_windowsize(wins);

	// save pointer to gui window: might be modified in event handling,
	// or could be modified if wins points to value in kill_list and kill_list is modified! nasty surprise
	gui_frame_t* gui = wins->gui;
	if(  gui  ) {
		event_t ev;

		ev.ev_class = INFOWIN;
		ev.ev_code = WIN_CLOSE;
		ev.mouse_pos.x = 0;
		ev.mouse_pos.y = 0;
		ev.click_pos.x = 0;
		ev.click_pos.y = 0;
		ev.mouse_button_state = 0;

		void *old = inside_event_handling;
		inside_event_handling = gui;
		wins->gui->infowin_event(&ev);
		inside_event_handling = old;
	}

	if(  (wins->wt&w_do_not_delete) == 0  ) {
		if(  wins->gui == gui  ) {
			// remove from kill list first, otherwise delete will be called again on that window
			for(  uint32 j = 0;  j < kill_list.get_count();  j++  ) {
				if(  kill_list[j].gui == gui  ) {
					kill_list.remove_at(j);
					break;
				}
			}
			delete gui;
		}
		else {
			// wins likely modified during event handling. Assume this was just a schedule window destroying itself, and top_win() therefore modifying wins.
			// return false to signal destroy_all_win() that wins was already modified.
			r = false;
		}
	}
	// set dirty flag to refill background
	if(  wl  ) {
		wl->set_background_dirty();
	}
	tooltip_text = 0;
	return r;
}


bool destroy_win(const ptrdiff_t magic)
{
	const gui_frame_t *gui = win_get_magic(magic);
	if(gui) {
		return destroy_win( gui );
	}
	return false;
}


bool destroy_win(const gui_frame_t *gui)
{
	for(  uint i=0;  i<wins.get_count();  i++  ) {
		if(wins[i].gui == gui) {
			if(inside_event_handling==wins[i].gui) {
				kill_list.append_unique(wins[i]);
			}
			else {
				simwin_t win = wins[i];
				wins.remove_at(i);
				save_win_position(win);
				destroy_framed_win(&win);
				if(  wins.get_count()>1  ) {
					notify_top_win();
				}
			}
			return true;
		}
	}
	return false;
}


void destroy_all_win(bool destroy_sticky)
{
	for(  sint32 curWin = 0;  curWin < (sint32)wins.get_count();  curWin++  ) {
		if(  destroy_sticky  ||  !wins[curWin].sticky  ) {
			save_win_position(wins[curWin]);
			if(  inside_event_handling == wins[curWin].gui  ) {
				// only add this, if not already added
				kill_list.append_unique(wins[curWin]);
				// compact the window list
				wins.remove_at(curWin);
				curWin--;
			}
			else {
				if(  destroy_framed_win(&wins[curWin])  ) {
					// compact the window list
					wins.remove_at(curWin);
					curWin--;
				}
				// else wins was already modified - assume by the schedule window closing itself during event handling
			}
		}
	}
}


void rollup_all_win()
{
	bool all_rolldown_flag = true; // If any dialog is open, all rolldown will not be performed
	for (  sint32 curWin = 0; curWin < (sint32)wins.get_count(); curWin++  ) {
		if (  !wins[curWin].rollup  ) {
			wins[curWin].rollup = true;
			gui_frame_t *gui = wins[curWin].gui;
			scr_size size = gui->get_windowsize();
			gfx->mark_rect_dirty_wc( wins[curWin].pos.x, wins[curWin].pos.y, wins[curWin].pos.x+size.w, wins[curWin].pos.y+size.h );
			all_rolldown_flag = false;
		}
	}

	if (  !all_rolldown_flag  ) {
		wl->set_background_dirty();
	}
	else if (  wins.get_count()  ) {
		rolldown_all_win();
	}
}


void rolldown_all_win()
{
	for (  sint32 curWin = 0; curWin < (sint32)wins.get_count(); curWin++  ) {
		wins[curWin].rollup = false;
		gui_frame_t *gui = wins[curWin].gui;
		scr_size size = gui->get_windowsize();
		gfx->mark_rect_dirty_wc( wins[curWin].pos.x, wins[curWin].pos.y, wins[curWin].pos.x+size.w, wins[curWin].pos.y+size.h );
	}
}


void display_win(int win)
{
	// ok, now process it
	gui_frame_t* comp = wins[win].gui;
	scr_size size = comp->get_windowsize();
	// minimising flag if resize allowed
	wins[win].flags.size = (comp->get_resizemode() != 0);
	scr_coord pos = wins[win].pos;
	FLAGGED_PIXVAL title_color = (comp->get_titlecolor()&0xFFFF);
	FLAGGED_PIXVAL text_color = env_t::front_window_text_color;
	if(  (unsigned)win!=wins.get_count()-1  ) {
		// not top => darker
		title_color = gfx->blend_colors(title_color, gfx->palette_lookup(COL_BLACK), env_t::bottom_window_darkness);
		text_color = env_t::bottom_window_text_color;
	}
	bool need_dragger = comp->get_resizemode() != gui_frame_t::no_resize;

	// HACK  So draw will know if gadget is needed.
	wins[win].flags.help = ( comp->get_help_filename() != NULL );
	if(  wins[win].flags.title  ) {
		win_draw_window_title(wins[win].pos,
				size,
				title_color,
				comp->get_name(),
				text_color,
				comp->get_weltpos(false),
				wins[win].gadget_state,
				wins[win].sticky,
				comp->is_weltpos(),
				wins[win].flags );
	}
	if(  wins[win].dirty  ) {
		// not sure this is still a useful call
		gfx->mark_rect_dirty_wc( wins[win].pos.x, wins[win].pos.y, wins[win].pos.x+size.w+1, wins[win].pos.y+2 );
		wins[win].dirty = false;
	}
	// mark top window, if requested
	if(env_t::window_frame_active  &&  (unsigned)win==wins.get_count()-1) {
		if(!wins[win].rollup) {
			gfx->draw_box3d_clipped( wins[win].pos.x-1, wins[win].pos.y-1, size.w+2, size.h+2, title_color, title_color);
		}
		else {
			gfx->draw_box3d_clipped( wins[win].pos.x-1, wins[win].pos.y-1, size.w+2, D_TITLEBAR_HEIGHT + 2, title_color, title_color);
		}
	}
	if(!wins[win].rollup) {
		comp->draw(wins[win].pos, size);

		// draw dragger
		if(need_dragger) {
			win_draw_window_dragger( pos, size);
		}
	}
}


void display_all_win()
{
	// first: empty kill list
	process_kill_list();

	// check which window can set tooltip
	const sint16 x = get_mouse_pos().x;
	const sint16 y = get_mouse_pos().y;
	tooltip_element = NULL;
	for(  uint32 i = wins.get_count(); i-- != 0;  ) {
		if(  (!wins[i].rollup  &&  wins[i].gui->is_hit(x-wins[i].pos.x,y-wins[i].pos.y))  ||
		     (wins[i].rollup  &&  x>=wins[i].pos.x  &&  x<wins[i].pos.x+wins[i].gui->get_windowsize().w  &&  y>=wins[i].pos.y  &&  y<wins[i].pos.y+D_TITLEBAR_HEIGHT)
		) {
			// tooltips are only allowed for this window
			tooltip_element = wins[i].gui;
			break;
		}
	}

	// then display windows
	for(  uint i=0;  i<wins.get_count();  i++  ) {
		void *old_gui = inside_event_handling;
		inside_event_handling = wins[i].gui;
		display_win(i);
		inside_event_handling = old_gui;
	}
}


void win_rotate90( sint16 new_ysize )
{
	for(simwin_t const& i : wins) {
		i.gui->map_rotate90(new_ysize);
	}
}


static void remove_old_win()
{
	// Destroy (close) old window when life time expire
	for(  int i=wins.get_count()-1;  i>=0;  i=min(i,(int)wins.get_count())-1  ) {
		if(wins[i].dauer > 0) {
			wins[i].dauer --;
			if(wins[i].dauer == 0) {
				destroy_win( wins[i].gui );
			}
		}
	}
}


static inline void snap_check_distance( scr_coord_val *r, const scr_coord_val a, const scr_coord_val b )
{
	if(  abs(a-b)<=env_t::window_snap_distance  ) {
		*r = a;
	}
}


static void snap_check_win( const int win, scr_coord *r, const scr_coord from_pos, const scr_coord from_size, const scr_coord to_pos, const scr_coord to_size )
{
	bool resize;
	if(  from_size==to_size  &&  from_pos!=to_pos  ) { // check if we're moving
		resize = false;
	}
	else if(  from_size!=to_size  &&  from_pos==from_pos  ) { // or resizing the window
		resize = true;
	}
	else {
		return; // or nothing to do.
	}

	const int wins_count = wins.get_count();

	for(  int i=0;  i<=wins_count;  i++  ) {
		if(  i==win  ) {
			// Don't snap to self
			continue;
		}

		scr_coord other_pos;
		scr_coord other_size;

		if(  i==wins_count  ) {
			// Allow snap to screen edge
			const scr_size screen = gfx->get_screen_size();

			other_pos.x = (env_t::menupos==MENU_LEFT)*env_t::iconsize.w;
			other_pos.y = (env_t::menupos==MENU_TOP)*env_t::iconsize.h + (env_t::menupos==MENU_BOTTOM)*win_get_statusbar_height();
			other_size.x = screen.w - other_pos.x - (env_t::menupos==MENU_RIGHT)*env_t::iconsize.w;
			other_size.y = screen.h - win_get_statusbar_height()-env_t::iconsize.h;
			if(  show_ticker  ) {
				other_size.y -= TICKER_HEIGHT;
			}
		}
		else {
			// Snap to other window
			other_size = wins[i].gui->get_windowsize();
			other_pos = wins[i].pos;
			if(  wins[i].rollup  ) {
				other_size.y = D_TITLEBAR_HEIGHT;
			}
		}

		// my bottom below other top  and  my top above other bottom  ---- in same vertical band
		if(  from_pos.y+from_size.y >= other_pos.y  &&  from_pos.y <= other_pos.y+other_size.y  ) {
			if(  resize  ) {
				// other right side and my new right side within snap
				snap_check_distance( &r->x, other_pos.x+other_size.x-from_pos.x, to_size.x );  // snap right - align right sides
			}
			else {
				// other right side and my new right side within snap
				snap_check_distance( &r->x, other_pos.x+other_size.x-from_size.x, to_pos.x );  // snap right - align right sides

				// other left side and my new left side within snap
				snap_check_distance( &r->x, other_pos.x, to_pos.x );  // snap left - align left sides
			}
		}

		// my new bottom below other top  and  my new top above other bottom  ---- in same vertical band
		if(  resize  ) {
			if(  from_pos.y+to_size.y>other_pos.y  &&  from_pos.y<other_pos.y+other_size.y  ) {
				// other left side and my new right side within snap
				snap_check_distance( &r->x, other_pos.x-from_pos.x, to_size.x );  // snap right - align my right to other left
			}
		}
		else {
			if(  to_pos.y+from_size.y>other_pos.y  &&  to_pos.y<other_pos.y+other_size.y  ) {
				// other left side and my new right side within snap
				snap_check_distance( &r->x, other_pos.x-from_size.x, to_pos.x );  // snap right - align my right to other left

				// other right side and my new left within snap
				snap_check_distance( &r->x, other_pos.x+other_size.x, to_pos.x );  // snap left - align my left to other right
			}
		}

		// my right side right of other left side  and  my left side left of other right side  ---- in same horizontal band
		if(  from_pos.x+from_size.x>=other_pos.x  &&  from_pos.x<=other_pos.x+other_size.x  ) {
			if(  resize  ) {
				// other bottom and my new bottom within snap
				snap_check_distance( &r->y, other_pos.y+other_size.y-from_pos.y, to_size.y );  // snap down - align bottoms
			}
			else {
				// other bottom and my new bottom within snap
				snap_check_distance( &r->y, other_pos.y+other_size.y-from_size.y, to_pos.y );  // snap down - align bottoms

				// other top and my new top within snap
				snap_check_distance( &r->y, other_pos.y, to_pos.y );  // snap up - align tops
			}
		}

		// my new right side right of other left side  and  my new left side left of other right side  ---- in same horizontal band
		if (  resize  ) {
			if(  from_pos.x+to_size.x>other_pos.x  &&  from_pos.x<other_pos.x+other_size.x  ) {
				// other top and my new bottom within snap
				snap_check_distance( &r->y, other_pos.y-from_pos.y, to_size.y );  // snap down - align my bottom to other top
			}
		}
		else {
			if(  to_pos.x+from_size.x>other_pos.x  &&  to_pos.x<other_pos.x+other_size.x  ) {
				// other top and my new bottom within snap
				snap_check_distance( &r->y, other_pos.y-from_size.y, to_pos.y );  // snap down - align my bottom to other top

				// other bottom and my new top within snap
				snap_check_distance( &r->y, other_pos.y+other_size.y, to_pos.y );  // snap up - align my top to other bottom
			}
		}
	}
}


static void move_win(int win, event_t *ev)
{
	const scr_coord mouse_from = ev->click_pos;
	const scr_coord mouse_to = ev->mouse_pos;

	const scr_coord from_pos = wins[win].pos;
	scr_coord from_size = scr_coord(wins[win].gui->get_windowsize().w,wins[win].gui->get_windowsize().h);
	if(  wins[win].rollup  ) {
		from_size.y = D_TITLEBAR_HEIGHT + 2;
	}

	scr_coord to_pos = wins[win].pos+(mouse_to-mouse_from);
	const scr_coord to_size = from_size;

	if(  env_t::window_snap_distance>0  ) {
		snap_check_win( win, &to_pos, from_pos, from_size, to_pos, to_size );
	}

	// CLIP(wert,min,max)
	win_clamp_xywh_position(wins[win].gui, to_pos, wins[win].gui->get_windowsize(), false);

	// delta is actual window movement.
	const scr_coord delta = to_pos - from_pos;

	wins[win].pos += delta;
	// need to mark all of old and new positions dirty. -1, +2 for env_t::window_frame_active
	gfx->mark_rect_dirty_wc( from_pos.x - 1, from_pos.y - 1, from_pos.x + from_size.x + 2, from_pos.y + from_size.y + 2 );
	wins[win].dirty = true;

	// set dirty flag to refill background
	if(wl) {
		wl->set_background_dirty();
	}

	change_drag_start( delta );
}


static void resize_win(int win, event_t *ev)
{
	event_t wev = *ev;
	wev.ev_class = WINDOW_RESIZE;
	wev.ev_code = 0;

	const scr_coord mouse_from = wev.click_pos;
	const scr_coord mouse_to  = wev.mouse_pos;

	const scr_coord from_pos = wins[win].pos;
	const scr_coord from_size = scr_coord(wins[win].gui->get_windowsize().w,wins[win].gui->get_windowsize().h);

	const scr_coord to_pos = from_pos;
	scr_coord to_size = from_size+(mouse_to-mouse_from);

	if(  env_t::window_snap_distance>0  ) {
		snap_check_win( win, &to_size, from_pos, from_size, to_pos, to_size );
	}

	// since we may be smaller afterwards
	gfx->mark_rect_dirty_wc( from_pos.x - 1, from_pos.y - 1, from_pos.x + from_size.x + 2, from_pos.y + from_size.y + 2 ); // -1, +2 for env_t::window_frame_active
	// set dirty flag to refill background
	if(wl) {
		wl->set_background_dirty();
	}

	// adjust event mouse scr_coord per snap
	wev.mouse_pos = wev.click_pos + to_size - from_size;

	wins[win].gui->infowin_event( &wev );
}


// returns true, if gui is a open window handle
static bool win_is_open(gui_frame_t *gui)
{
	for(simwin_t const& i : wins) {
		if (i.gui == gui) {
			for(simwin_t const& j : kill_list) {
				if (j.gui == gui) {
					return false;
				}
			}
			return true;
		}
	}
	return false;
}


scr_coord const& win_get_pos(gui_frame_t const* const gui)
{
	for(  uint32 i = wins.get_count(); i-- != 0;  ) {
		if(  wins[i].gui == gui  ) {
			return wins[i].pos;
		}
	}
	static scr_coord const bad(-1, -1);
	return bad;
}


void win_set_pos(gui_frame_t *gui, scr_coord new_pos)
{
	for(  uint32 i = wins.get_count(); i-- != 0;  ) {
		if(  wins[i].gui == gui  ) {
			win_clamp_xywh_position(gui, new_pos, gui->get_windowsize(), true);
			wins[i].pos   = new_pos;
			wins[i].dirty = true;
			return;
		}
	}
}


bool last_drag_is_caught = false;

// since check_pos_win is processed before i.e. scrolling map
// we do not want to catch the mouse, if we use it already
void catch_dragging()
{
	last_drag_is_caught = true;
}


/*
 * main window event handler
 */
bool check_pos_win(event_t *ev,bool modal)
{
	static int is_resizing = -1;
	static int is_moving = -1;
	static bool is_dragging = false;

	bool swallowed = false;

	const int x = ev->ev_class==EVENT_MOVE?ev->mouse_pos.x:ev->click_pos.x;
	const int y = ev->ev_class==EVENT_MOVE?ev->mouse_pos.y:ev->click_pos.y;

	if( last_drag_is_caught ) {
		if( ev->ev_class == EVENT_DRAG ) {
			// somebody else drags already => do nothing
			return false;
		}
		if( ev->ev_class == EVENT_RELEASE ) {
			// we will handle dragging events again after this
			last_drag_is_caught = false;
			return false;
		}
	}

	// for the moment, no none events
	if (ev->ev_class == EVENT_NONE) {
		process_kill_list();
		return false;
	}

	// we stop resizing once the user releases the button
	if(  (is_resizing>=0  ||  is_moving>=0)  &&  (IS_LEFTRELEASE(ev)  ||  (ev->mouse_button_state&1)==0)  ) {
		is_resizing = -1;
		is_moving = -1;
		if(  IS_LEFTRELEASE(ev)  ) {
			// should not proceed, otherwise the left release event will be fed to other components;
			// return true (i.e. event swallowed) to prevent propagation back to the main view
			return true;
		}
	}

	// disable any active tooltip upon mouse click by forcing expiration of tooltip duration
	if(  ev->ev_class==EVENT_CLICK  ) {
		tooltip_register_time = 0;
	}

	// click in main menu?
	const scr_size screen = gfx->get_screen_size();
	scr_coord menuoffset((env_t::menupos == MENU_RIGHT) * (screen.w - env_t::iconsize.w), (env_t::menupos == MENU_BOTTOM) * (screen.h - env_t::iconsize.h) - D_TITLEBAR_HEIGHT);

	if (!tool_t::toolbar_tool.empty()  &&
		tool_t::toolbar_tool[0]->get_tool_selector()  &&
		(is_dragging  ||  tool_t::toolbar_tool[0]->get_tool_selector()->is_hit(x-menuoffset.x, y-menuoffset.y))  &&
		y > menuoffset.y+D_TITLEBAR_HEIGHT  &&  (ev->ev_class >= EVENT_CLICK  &&  ev->ev_class <= EVENT_DRAG)  ) {

		// first finish dragging
		event_t wev = *ev;
		wev.move_origin(menuoffset);

		inside_event_handling = tool_t::toolbar_tool[0];
		tool_t::toolbar_tool[0]->get_tool_selector()->infowin_event(&wev);
		inside_event_handling = NULL;

		if (!tool_t::toolbar_tool[0]->get_tool_selector()->is_hit(ev->mouse_pos.x, ev->mouse_pos.y)) {

			// find out if the toolbar should be move to another corner
			uint8 new_pos = 0;
			int mx = get_mouse_pos().x; // inclippe values needed
			int my = get_mouse_pos().y;
			if (my < env_t::iconsize.h) {
				new_pos = 1 << MENU_TOP;
			}
			if (my > screen.h- env_t::iconsize.h) {
				new_pos |= 1 << MENU_BOTTOM;
			}
			if (mx < env_t::iconsize.w) {
				new_pos |= 1 << MENU_LEFT;
			}
			if (mx > screen.w - env_t::iconsize.w) {
				new_pos |= 1 << MENU_RIGHT;
			}
			if (new_pos  &&  new_pos!=(1<<env_t::menupos)) {
				// check for unit rotation and convert to MNEU_XXX system
				uint8 check_pos = new_pos, rot = 0;
				while (  (check_pos & 1)==0) {
					check_pos >>= 1;
					rot++;
				}
				// single rotation only
				if (check_pos == 1  &&  rot!=env_t::menupos) {
					env_t::menupos = rot;
					world()->set_dirty();

					// if no inside a modal from
					if (!modal) {
						// move all windows
						event_t* ev = new event_t();
						ev->ev_class = EVENT_SYSTEM;
						ev->ev_code = SYSTEM_RELOAD_WINDOWS;
						queue_event(ev);
					}

					is_dragging = false;
					return true;
				}
			}
		}

		is_dragging = ev->ev_class != EVENT_RELEASE  &&  ev->mouse_button_state>1;

		// swallow event
		return true;
	}

	// cursor event only go to top window (but not if rolled up)
	if(  (IS_KEYDOWN(ev)  ||  ev->ev_class == EVENT_STRING)  &&  !wins.empty()  ) {
		simwin_t &win  = wins.back();
		if(  !win.rollup  )  {
			inside_event_handling = win.gui;
			swallowed = win.gui->infowin_event(ev);
		}
		inside_event_handling = NULL;
		process_kill_list();
		return swallowed;
	}

	// just move top window until button release
	if(  is_moving>=0  &&  (unsigned)is_moving<wins.get_count()  &&  (IS_LEFTDRAG(ev)  )  ) {
		move_win( is_moving, ev );
		return true;
	}

	// just resize window until button release
	if(  is_resizing>=0  &&  (unsigned)is_resizing<wins.get_count()  &&  (IS_LEFTDRAG(ev)  )  ) {
		resize_win( is_resizing, ev );
		return true;
	}

	// swallow all other events in the infobar
	if(  !IS_KEYDOWN(ev)  &&  ev->ev_class != EVENT_STRING  &&  y > gfx->get_screen_size().h - win_get_statusbar_height()  ) {
		// swallow event
		return true;
	}

	// swallow all other events in ticker (if there)
	if(  !IS_KEYDOWN(ev)  &&  ev->ev_class != EVENT_STRING  &&  show_ticker  &&  y > gfx->get_screen_size().h - win_get_statusbar_height() - TICKER_HEIGHT  ) {
		if(  IS_LEFTCLICK(ev)  ) {
			ticker::process_click(x);
		}
		// swallow event
		return true;
	}

	// handle all the other events
	for(  int i=wins.get_count()-1;  i>=0  &&  !swallowed;  i=min(i,(int)wins.get_count())-1  ) {

		if(  wins[i].gui->is_hit( x-wins[i].pos.x, y-wins[i].pos.y )  ) {

			// all events in window are swallowed
			swallowed = true;

			inside_event_handling = wins[i].gui;

			// Top window first
			if(  (int)wins.get_count()-1>i  &&  IS_LEFTCLICK(ev)  &&  (!wins[i].rollup  ||  ev->click_pos.y<wins[i].pos.y+D_TITLEBAR_HEIGHT)  ) {
				i = top_win(i,false);
			}

			// if within title bar && window needs decoration
			// Max Kielland: Use title height
			if(  y<wins[i].pos.y+D_TITLEBAR_HEIGHT  &&  wins[i].flags.title  ) {
				// no more moving
				is_moving = -1;
				wins[i].dirty = true;

				// HACK So decode will know if gadget is needed.
				wins[i].flags.help = ( wins[i].gui->get_help_filename() != NULL );

				// Where Was It ?
				sint8 code = decode_gadget_boxes( ( & wins[i].flags ), wins[i].pos.x + (REVERSE_GADGETS?0:wins[i].gui->get_windowsize().w-D_GADGET_WIDTH), x );

				if(  code < SKIN_GADGET_COUNT  ) {
					if(  IS_LEFTCLICK(ev)  ) {
						wins[i].gadget_state |= (1 << code);
					}
					else if(  IS_LEFTRELEASE(ev)  ) {
						wins[i].gadget_state &= ~(1 << code);
						if(  ev->mouse_pos.y >= wins[i].pos.y  &&  ev->mouse_pos.y < wins[i].pos.y+D_TITLEBAR_HEIGHT  &&  decode_gadget_boxes( ( & wins[i].flags ), wins[i].pos.x + (REVERSE_GADGETS?0:wins[i].gui->get_windowsize().w-D_GADGET_WIDTH), ev->mouse_pos.x )==code  ) {
							// do whatever needs to be done
							switch(  code  ) {
								case SKIN_GADGET_CLOSE :
									destroy_win(wins[i].gui);
									break;
								case SKIN_GADGET_MINIMIZE: // (Mathew Hounsell)
									ev->ev_class = WINDOW_MAKE_MIN_SIZE;
									ev->ev_code = 0;
									wl->set_background_dirty();
									wins[i].gui->infowin_event( ev );
									break;
								case SKIN_GADGET_HELP :
									help_frame_t::open_help_on( wins[i].gui->get_help_filename() );
									break;
								case SKIN_BUTTON_PREVIOUS:
									ev->ev_class = WINDOW_CHOOSE_NEXT;
									ev->ev_code = PREV_WINDOW;  // backward
									wins[i].gui->infowin_event( ev );
									break;
								case SKIN_BUTTON_NEXT:
									ev->ev_class = WINDOW_CHOOSE_NEXT;
									ev->ev_code = NEXT_WINDOW;  // forward
									wins[i].gui->infowin_event( ev );
									break;
								case SKIN_GADGET_GOTOPOS:
									{
										// change position on map (or follow)
										koord3d k = wins[i].gui->get_weltpos(true);
										if(  k!=koord3d::invalid  ) {
											wl->get_viewport()->change_world_position( k );
											wl->get_zeiger()->change_pos( k );
										}
									}
									break;
								case SKIN_GADGET_NOTPINNED:
									wins[i].sticky = !wins[i].sticky;
									break;
							}
						}
					}
				}
				else {
					// Somewhere on the titlebar
					if (IS_LEFTDRAG(ev)) {
						i = top_win(i,false);
						move_win(i, ev);
						is_moving = i;
					}
					if(IS_RIGHTCLICK(ev)  ||  IS_LEFTDBLCLK(ev)  ) {
						wins[i].rollup ^= 1;
						gui_frame_t *gui = wins[i].gui;
						scr_size size = gui->get_windowsize();
						gfx->mark_rect_dirty_wc( wins[i].pos.x, wins[i].pos.y, wins[i].pos.x+size.w, wins[i].pos.y+size.h );
						if(  wins[i].rollup  ) {
							wl->set_background_dirty();
						}
					}
				}

				// It has been handled so stop checking.
				break;

			}
			else {
				if(!wins[i].rollup) {
					// click in Window / Resize?
					//11-May-02   markus weber added

					scr_size size = wins[i].gui->get_windowsize();

					// resizer hit ?
					const bool canresize = is_resizing>=0  ||
												(ev->click_pos.x > wins[i].pos.x + size.w - D_DRAGGER_WIDTH  &&
												 ev->click_pos.y > wins[i].pos.y + size.h - D_DRAGGER_HEIGHT);

					if((IS_LEFTCLICK(ev)  ||  IS_LEFTDRAG(ev))  &&  canresize  &&  wins[i].gui->get_resizemode()!=gui_frame_t::no_resize) {
						resize_win( i, ev );
						is_resizing = i;
					}
					else {
						is_resizing = -1;
						// click in Window
						event_t wev = *ev;
						wev.move_origin(wins[i].pos);
						wins[i].gui->infowin_event( &wev );
					}
				}
				else {
					swallowed = false;
				}
			}
			inside_event_handling = NULL;
		}
	}

	inside_event_handling = NULL;
	process_kill_list();

	return swallowed;
}


void win_poll_event(event_t* const ev)
{
	display_poll_event(ev);
	// main window resized
	if(  ev->ev_class==EVENT_SYSTEM  &&  ev->ev_code==SYSTEM_RESIZE  ) {
		// main window resized
		gfx->on_window_resized( ev->new_window_size );

		const scr_size screen = gfx->get_screen_size();

		gui_theme_t::gui_buttons_per_row = max(2, min(4, screen.w / (D_BUTTON_WIDTH + D_H_SPACE + D_MARGIN_LEFT)));
		ticker::redraw();
		tool_t::update_toolbars();

		for( uint i = 0; i<wins.get_count(); i++ ) {
			win_clamp_xywh_position(wins[i].gui, wins[i].pos, wins[i].gui->get_min_windowsize(), true );
		}

		wl->set_dirty();
		wl->get_viewport()->metrics_updated();
		ev->ev_class = IGNORE_EVENT;

		// now see how much of the status text fits the display
		scr_coord_val time_width = gfx->calc_text_width(tick_to_string(wl->get_ticks()));
		scr_coord_val player_width = gfx->calc_text_width(wl->get_active_player()->get_name());

		char buffer[128];
		if (env_t::player_finance_display_account) {
			money_to_string(buffer, (double)world()->get_active_player()->get_finance()->get_account_balance() / 100.0);
		}
		else {
			money_to_string(buffer, (double)world()->get_active_player()->get_finance()->get_netwealth() / 100.0);
		}

		scr_coord_val money_width = gfx->calc_text_width(buffer);
		scr_coord_val position_width = gfx->calc_text_width(wl->get_zeiger()->get_pos().get_str());

		// if too long shorten radically
		status_show_compact = (time_width + player_width + money_width + position_width) * 1.2 > ev->new_window_size.w;
	}
	// save and reload all windows (currently only used when a new theme is applied)
	else if(  ev->ev_class==EVENT_SYSTEM  &&  ev->ev_code==SYSTEM_RELOAD_WINDOWS  ) {
		dr_chdir( env_t::user_dir );
		loadsave_t dlg;

		if(  dlg.wr_open( "dlgpos.xml", loadsave_t::xml_zipped, 1, "temp", SERVER_SAVEGAME_VER_NR ) == loadsave_t::FILE_STATUS_OK  ) {
			// save all
			rdwr_all_win( &dlg );
			dlg.close();
			destroy_all_win( true );
			if(  dlg.rd_open( "dlgpos.xml" ) == loadsave_t::FILE_STATUS_OK  ) {
				// and reload them ...
				rdwr_all_win( &dlg );
			}
		}
		wl->set_dirty();
		ev->ev_class = IGNORE_EVENT;
		ticker::redraw();
	}
	else if(  ev->ev_class==EVENT_SYSTEM  &&  ev->ev_code==SYSTEM_THEME_CHANGED  ) {
		// called when font is changed
		ev->mouse_pos.x = ev->mouse_pos.y = ev->click_pos.x = ev->click_pos.y = 0;
		for(simwin_t const& i : wins) {
			i.gui->infowin_event(ev);
		}
		ev->ev_class = IGNORE_EVENT;
		ticker::redraw();
	}
}


uint16 win_get_statusbar_height()
{
	return max(LINESPACE + 2, 15);
}


// finally updates the display
void win_display_flush(double konto)
{
	const scr_size screen = gfx->get_screen_size();

	// display main menu
	tool_selector_t *main_menu = tool_t::toolbar_tool[0]->get_tool_selector();
	scr_coord menu_pos(0,0);
	scr_size menu_size(screen.w, env_t::iconsize.h);
	scr_rect clip_rr(0, env_t::iconsize.h, screen.w, screen.h - env_t::iconsize.h);

	switch (env_t::menupos) {
		case MENU_TOP:
			// pos default (see above)
			// size default
			// rect default
			break;

		case MENU_BOTTOM:
			menu_pos = scr_coord(0, screen.h - env_t::iconsize.h);
			// size default
			clip_rr.y = 0;
			break;

		case MENU_LEFT:
			// pos default (see above)
			menu_size = scr_size(env_t::iconsize.w, screen.h - win_get_statusbar_height()-show_ticker*TICKER_HEIGHT);
			clip_rr = scr_rect(env_t::iconsize.h, 0, screen.w - env_t::iconsize.w, screen.h);
			break;

		case MENU_RIGHT:
			menu_pos.x = screen.h - env_t::iconsize.w;
			menu_size = scr_size(env_t::iconsize.w, screen.h - win_get_statusbar_height()-show_ticker*TICKER_HEIGHT );
			clip_rr = scr_rect(0, 0, screen.w - env_t::iconsize.w, screen.h);
			break;
	}

	gfx->set_clip_rect( menu_pos.x, menu_pos.y, menu_size.w, menu_size.h CLIP_NUM_DEFAULT, false);

	if(  skinverwaltung_t::toolbar_background  &&  skinverwaltung_t::toolbar_background->get_image_id(0) != IMG_EMPTY  ) {
		const image_id back_img = skinverwaltung_t::toolbar_background->get_image_id(0);
		gfx->fit_img_to_width( back_img, env_t::iconsize.w );

		stretch_map_t imag = {
			{ IMG_EMPTY, IMG_EMPTY, IMG_EMPTY },
			{ IMG_EMPTY, back_img,  IMG_EMPTY },
			{ IMG_EMPTY, IMG_EMPTY, IMG_EMPTY }
		};
		gfx->draw_stretch_map(imag, scr_rect(menu_pos, menu_size));
	}
	else {
		gfx->draw_rect( menu_pos.x, menu_pos.y, menu_size.w, menu_size.h, gfx->palette_lookup(MN_GREY2), false );
	}

	// .. extra logic to enable tooltips
	tooltip_element = main_menu->is_hit( get_mouse_pos().x-menu_pos.x, get_mouse_pos().y-menu_pos.y) ? main_menu : NULL;
	void *old_inside_event_handling = inside_event_handling;
	inside_event_handling = main_menu;
	menu_pos.y -= D_TITLEBAR_HEIGHT;
	main_menu->draw(menu_pos, menu_size);
	inside_event_handling = old_inside_event_handling;

	gfx->set_clip_rect(clip_rr.x, clip_rr.y, clip_rr.w, clip_rr.h CLIP_NUM_DEFAULT, false);

	if(show_ticker  ||  !ticker::empty()) {
		if (!show_ticker  ||  wl->is_dirty()) {
			ticker::redraw();
		}
		show_ticker = !ticker::empty();
		ticker::draw();
	}
	else {
		show_ticker = false;
	}

	if(  skinverwaltung_t::compass_iso  &&  env_t::compass_screen_position  ) {
		const image_id img = skinverwaltung_t::compass_iso->get_image_id(wl->get_settings().get_rotation());
		const scr_rect area = {
			D_MARGIN_LEFT,
			env_t::iconsize.h+D_MARGIN_TOP,
			screen.w - 2*4,
			screen.h - env_t::iconsize.h - D_MARGIN_TOP - D_MARGIN_BOTTOM - win_get_statusbar_height() - (TICKER_HEIGHT)*show_ticker
		};

		gfx->draw_img_aligned(img, area, env_t::compass_screen_position, false );
	}

	{
		// clip windows to avoid drawing into menu, ticker, or status-bar
		PUSH_CLIP(clip_rr.x, clip_rr.y+(env_t::menupos==MENU_BOTTOM ? win_get_statusbar_height() + show_ticker*TICKER_HEIGHT:0), clip_rr.w, clip_rr.h - (TICKER_HEIGHT)*show_ticker - win_get_statusbar_height() );

		display_all_win();
		remove_old_win();

		if(env_t::show_tooltips) {
			// check if there is a tooltip to display
			if(  tooltip_text  &&  *tooltip_text  ) {
				// display tooltip when current owner is invalid or when it is within visible duration
				uint32 elapsed_time;
				if(  !tooltip_owner  ||  ((elapsed_time=dr_time()-tooltip_register_time)>env_t::tooltip_delay  &&  elapsed_time<=env_t::tooltip_delay+env_t::tooltip_duration)  ) {
					const sint16 width = gfx->calc_text_width(tooltip_text)+(LINESPACE/2);
					scr_coord pos{ tooltip_xpos, tooltip_ypos };
					win_clamp_xywh_position( NULL, pos, scr_size( width, (LINESPACE*9)/7 ), true );
					gfx->draw_textbox3d_clipped( pos.x, pos.y, env_t::tooltip_color, env_t::tooltip_textcolor, tooltip_text, true CLIP_NUM_DEFAULT);
					if(wl) {
						wl->set_background_dirty();
					}
				}
			}
			else if(!static_tooltip_text.empty()) {
				const sint16 width = gfx->calc_text_width(static_tooltip_text.c_str())+ (LINESPACE/2);
				scr_coord pos = get_mouse_pos();
				win_clamp_xywh_position( NULL, pos, scr_size(width, (LINESPACE*9)/7), true);
				gfx->draw_textbox3d_clipped(pos.x, pos.y, env_t::tooltip_color, env_t::tooltip_textcolor, static_tooltip_text.c_str(), true CLIP_NUM_DEFAULT);
				if(wl) {
					wl->set_background_dirty();
				}
			}
			// reset owner and group if no tooltip has been registered
			if(  !tooltip_text  ) {
				tooltip_owner = 0;
				tooltip_group = 0;
			}
			// clear tooltip text to avoid sticky tooltips
			tooltip_text = 0;
		}

		POP_CLIP();

		if(!wl) {
			// no infos during loading etc
			return;
		}
	}

	char const* time;
	if (status_show_compact  &&  env_t::show_month >= env_t::DATE_FMT_JAPANESE  &&  env_t::show_month <= env_t::DATE_FMT_GERMAN) {
		// omit season from the date string
		env_t::show_month += 3;
		time = tick_to_string(wl->get_ticks());
		env_t::show_month -= 3;
	}
	else {
		time = tick_to_string(wl->get_ticks());
	}

	// statusbar background
	scr_coord_val const status_bar_height = win_get_statusbar_height();
	scr_coord_val const status_bar_y =env_t::menupos == MENU_BOTTOM ? 0 : screen.h - status_bar_height;
	scr_coord_val const status_bar_text_y = status_bar_y + (status_bar_height - LINESPACE) / 2;
	scr_coord_val const status_bar_icon_y = status_bar_y + (status_bar_height - 15) / 2;

	gfx->set_clip_rect( 0, 0, screen.w, screen.h CLIP_NUM_DEFAULT, false);
	gfx->draw_rect(0, status_bar_y - 1, screen.w, 1, SYSCOL_STATUSBAR_DIVIDER, false);
	gfx->draw_rect(0, env_t::menupos == MENU_BOTTOM ? status_bar_height : status_bar_y - 1, screen.w, 1, SYSCOL_STATUSBAR_DIVIDER, false);
	gfx->draw_rect(0, status_bar_y, screen.w, status_bar_height, SYSCOL_STATUSBAR_BACKGROUND, false);

	bool tooltip_check = env_t::menupos == MENU_BOTTOM ? get_mouse_pos().y < status_bar_height : get_mouse_pos().y > status_bar_y;
	if(  tooltip_check  ) {
		tooltip_xpos = get_mouse_pos().x;
		tooltip_ypos = env_t::menupos == MENU_BOTTOM ? status_bar_height + 10 + TICKER_HEIGHT * show_ticker : status_bar_y - 10 - TICKER_HEIGHT * show_ticker;
	}

	scr_coord_val left_border = 2;

	// season icon
	gfx->draw_color_img( skinverwaltung_t::seasons_icons->get_image_id(wl->get_season()), left_border, status_bar_icon_y, 0, false, true CLIP_NUM_DEFAULT);
	if(  tooltip_check  &&  tooltip_xpos<14  ) {
		static char const* const seasons[] = { "q2", "q3", "q4", "q1" };
		tooltip_text = translator::translate(seasons[wl->get_season()]);
		tooltip_check = false;
	}
	left_border += 14;

	scr_coord_val right_border = screen.w - 4;

	// shown if timeline game
	if(  wl->use_timeline()  &&  skinverwaltung_t::timelinesymbol  ) {
		right_border -= 14;
		gfx->draw_color_img( skinverwaltung_t::timelinesymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true CLIP_NUM_DEFAULT);
		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
			tooltip_text = translator::translate("timeline");
			tooltip_check = false;
		}
	}

	// shown if connected
	if(  env_t::networkmode  &&  skinverwaltung_t::networksymbol  ) {
		right_border -= 14;
		gfx->draw_color_img( skinverwaltung_t::networksymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true CLIP_NUM_DEFAULT);
		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
			tooltip_text = translator::translate("Connected with server");
			tooltip_check = false;
		}
	}

	// put pause icon
	if(  wl->is_paused()  &&  skinverwaltung_t::pausesymbol  ) {
		right_border -= 14;
		gfx->draw_color_img( skinverwaltung_t::pausesymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true CLIP_NUM_DEFAULT);
		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
			tooltip_text = translator::translate("GAME PAUSED");
			tooltip_check = false;
		}
	}

	// put fast forward icon
	if(  wl->is_fast_forward()  &&  skinverwaltung_t::fastforwardsymbol  ) {
		right_border -= 14;
		gfx->draw_color_img( skinverwaltung_t::fastforwardsymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true CLIP_NUM_DEFAULT);
		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
			tooltip_text = translator::translate("Fast forward");
			tooltip_check = false;
		}
	}
	right_border -= 4;

	koord3d pos = wl->get_zeiger()->get_pos();

	static cbuffer_t info;
	info.clear();
	if(  pos!=koord3d::invalid  ) {
		info.printf( "(%s)", pos.get_str() );
	}
	if(  skinverwaltung_t::timelinesymbol==NULL  ) {
		info.printf( " %s", translator::translate(wl->use_timeline()?"timeline":"no timeline") );
	}
	if(wl->show_distance!=koord3d::invalid  &&  wl->show_distance!=pos) {
		info.printf("-(%d,%d)", wl->show_distance.x-pos.x, wl->show_distance.y-pos.y );
	}
	if(  !env_t::networkmode  ) {
		// time multiplier text
		if(wl->is_fast_forward()) {
			info.printf(" %s(T~%1.2f)", skinverwaltung_t::fastforwardsymbol?"":">> ", wl->get_simloops()/50.0 );
		}
		else if(!wl->is_paused()) {
			if (wl->get_time_multiplier() != 16) {
				info.printf(" (T=%1.2f)", wl->get_time_multiplier() / 16.0);
			}
		}
		else if(  skinverwaltung_t::pausesymbol==NULL  ) {
			info.printf( " %s", translator::translate("GAME PAUSED") );
		}
	}
#ifdef DEBUG
	if(  env_t::verbose_debug>1  ) {
		if(  haltestelle_t::get_rerouting_status()==RECONNECTING  ) {
			info.append( " +" );
		}
		else if(  haltestelle_t::get_rerouting_status()==REROUTING  ) {
			info.append( " *" );
		}
		if(  skinverwaltung_t::compass_iso == NULL  &&  wl->get_settings().get_rotation()  ) {
			static const char *compass_dir[4] = { "North", "East", "South", "West" };
			info.append( " " );
			info.append( translator::translate( compass_dir[ 4-wl->get_settings().get_rotation() ] ) );
		}
	}
#endif

	left_border  += 4 + gfx->draw_text(left_border,  status_bar_text_y, time, ALIGN_LEFT,  SYSCOL_STATUSBAR_TEXT, true);
	right_border -= 4 + gfx->draw_text(right_border, status_bar_text_y, info, ALIGN_RIGHT, SYSCOL_STATUSBAR_TEXT, true);

	/* Since the visual center jumps left and right with proportional fonts, we quantisze by 16 pixel
	 */
	left_border = (left_border + 15) & 0xFFFFFFF0ul;
	right_border = (right_border - 15) & 0xFFFFFFF0ul;

	if(wl->get_active_player()) {
		char buffer[256];
		scr_coord_val textwidth_pl = 0;
		textwidth_pl = D_H_SPACE + gfx->calc_text_width(wl->get_active_player()->get_name());
		if (status_show_compact) {
			number_to_string_fit(buffer, konto, 0, 10);
			strcat(buffer, "$");
		}
		else {
			money_to_string(buffer, konto);
		}

		scr_coord_val textwidth_mn = (gfx->calc_text_width(buffer) + 15) & 0xFFFFFFF0ul;
		if (textwidth_mn + textwidth_pl < right_border - left_border) {
			// everything fits
			left_border += ( (right_border - left_border) - (textwidth_mn + textwidth_pl)  ) / 2;
			left_border += D_H_SPACE + gfx->draw_text(left_border, status_bar_text_y, wl->get_active_player()->get_name(), ALIGN_LEFT, PLAYER_FLAG | gfx->palette_lookup(wl->get_active_player()->get_player_color1() + env_t::gui_player_color_dark), true);
			gfx->draw_text(left_border, status_bar_text_y, buffer, ALIGN_LEFT, konto >= 0.0 ? MONEY_PLUS : MONEY_MINUS, true);
		}
		else {
			// no space => only money
			status_show_compact = true; // make sure the seasons are omitted next time
			scr_coord_val width_left = (right_border - left_border - textwidth_mn);
			if (width_left > 50) {
				scr_rect r(left_border, status_bar_text_y, width_left-D_H_SPACE, LINESPACE);
				gfx->draw_text_ellipsis(r, wl->get_active_player()->get_name(), ALIGN_LEFT, PLAYER_FLAG | gfx->palette_lookup(wl->get_active_player()->get_player_color1() + env_t::gui_player_color_dark), true);
				left_border += width_left;
				// normal color
				gfx->draw_text(left_border, status_bar_text_y, buffer, ALIGN_LEFT, konto >= 0.0 ? MONEY_PLUS : MONEY_MINUS, true);
			}
			else {
				// just money in player color, no player name
				if (width_left > 0) {
					left_border += (right_border - left_border - textwidth_mn) / 2;
				}
				else {
					// shortest possible money strong
					number_to_string_fit(buffer, konto, 0, 5);
					strcat(buffer, "$");
				}
				gfx->draw_text(left_border, status_bar_text_y, buffer, ALIGN_LEFT, konto >= 0.0 ? gfx->palette_lookup(wl->get_active_player()->get_player_color1() + env_t::gui_player_color_dark) : MONEY_MINUS, true);
			}
		}
	}
}


void win_set_world(karte_t *world)
{
	wl = world;
	// remove all save window positions
	old_win_pos.clear();
}


void win_redraw_world()
{
	if(wl) {
		wl->set_dirty();
	}
}


bool win_change_zoom_factor(bool magnify)
{
	const bool result = magnify ? gfx->zoom_factor_up() : gfx->zoom_factor_down();

	if(magnify  &&  wl->is_step_mode_normal()) {
		wl->reset_timer();
	}

	wl->get_viewport()->metrics_updated();

	return result;
}


void win_load_font(const char *fname, uint8 fontsize)
{
	bool force_reload = fontsize != env_t::fontsize;
	env_t::fontsize = fontsize;

	if (gfx->load_font(fname, force_reload) ) {
		// successfull
		gui_theme_t::themes_init( env_t::default_theme, false, false );

		event_t *ev = new event_t();
		ev->ev_class = EVENT_SYSTEM;
		ev->ev_code = SYSTEM_THEME_CHANGED;
		queue_event( ev );
	}
	else {
		// restore old font
		gfx->load_font(env_t::fontname.c_str(), true);
	}
	win_redraw_world();
}


/**
 * Sets the tooltip to display.
 * Has to be called from within gui_frame_t::draw
 *
 * @param pos
 * @param text
 * @param group
 * @param owner : owner==NULL disables timing (initial delay and visible duration)
 */
void win_set_tooltip(scr_coord pos, const char *text, const void *const owner, const void *const group)
{
	// check whether the right window will set the tooltip
	if(inside_event_handling != tooltip_element  ) {
		return;
	}
	// must be set every time as win_display_flush() will reset them
	tooltip_text = text;

	// update ownership if changed
	if(  owner!=tooltip_owner  ) {
		tooltip_owner = owner;
		// update register time only if owner is valid
		if(  owner  ) {
			const uint32 current_time = dr_time();
			if(  group  &&  group==tooltip_group  ) {
				// case : same group
				const uint32 elapsed_time = current_time - tooltip_register_time;
				const uint32 threshold = env_t::tooltip_delay - (env_t::tooltip_delay>>2); // 3/4 of delay
				if(  elapsed_time>threshold  &&  elapsed_time<=env_t::tooltip_delay+env_t::tooltip_duration  ) {
					// case : threshold was reached and duration not expired -> delay time is reduced to 1/4
					tooltip_register_time = current_time - threshold;
				}
				else {
					// case : either before threshold or duration expired
					tooltip_register_time = current_time;
				}
			}
			else {
				// case : owner has no associated group or group is different -> simply reset to current time
				tooltip_group = group;
				tooltip_register_time = current_time;
			}
		}
		else {
			// no owner to associate with a group even if the group is valid
			tooltip_group = 0;
		}
	}

	if (text) {
		const scr_size tt_size = scr_size(gfx->calc_text_width(text), LINESPACE + 2);
		win_clamp_xywh_position(NULL, pos, tt_size, true);
		pos.y += LINESPACE / 2 + 1;
	}

	tooltip_xpos = pos.x;
	tooltip_ypos = pos.y;
}


/**
 * Sets the tooltip to display.
 */
void win_set_static_tooltip(const char *text)
{
	static_tooltip_text = text ? text : "";
}


// shows a modal dialoge
void modal_dialogue(gui_frame_t* gui, ptrdiff_t magic, karte_t* welt, bool (*quit)(), bool dismissible)
{
	const scr_size screen = gfx->get_screen_size();

	if (screen.w == 0) {
		dbg->error("modal_dialogue", "called without a display driver => nothing will be shown!");
		env_t::quit_simutrans = true;
		// cannot handle this!
		return;
	}

	// switch off autosave
	sint32 old_autosave = env_t::autosave;
	env_t::autosave = 0;

	event_t ev;
	create_win(scr_coord(0,0), gui, w_info, magic);
	scr_coord pos{
		(screen.w - gui->get_windowsize().w) / 2,
		(screen.h - gui->get_windowsize().h) / 2
	};
	win_clamp_xywh_position(gui, pos, gui->get_windowsize(), true);
	wins[wins[0].gui!=gui].pos = pos;

	if (welt) {
		welt->set_pause(false);
		welt->reset_interaction();
		welt->reset_timer();
		set_random_mode(MODAL_RANDOM);

		const uint32 ms_per_frame = 1000 / env_t::fps;
		const uint32 sync_steps_per_step = 5; // env_t::network_frames_per_step
		uint32 frame_start_time;
		uint32 sync_steps_until_step = sync_steps_per_step;

		while (gui && win_is_open(gui) && !env_t::quit_simutrans && !quit()) {
			frame_start_time = dr_time();

			do {
				DBG_DEBUG4("modal_dialogue", "calling win_poll_event");
				win_poll_event(&ev);

				if (ev.ev_class == EVENT_SYSTEM && ev.ev_code == SYSTEM_QUIT) {
					welt->stop(true);
					break;
				}

				win_clamp_xywh_position(NULL, ev.mouse_pos, scr_size(1, 1), false);
				win_clamp_xywh_position(NULL, ev.mouse_pos, scr_size(1, 1), false);

				if (IS_KEYDOWN(&ev) && ev.ev_code == SIM_KEYCODE_F1) {
					if (gui_frame_t* win = win_get_top()) {
						if (const char* helpfile = win->get_help_filename()) {
							help_frame_t::open_help_on(helpfile);
							continue;
						}
					}
				}

				if(dismissible  &&  gui){
					if (
						(IS_KEYDOWN(&ev)) ||
						(ev.ev_class == EVENT_CLICK  &&  !gui->is_hit(ev.click_pos.x - pos.x, ev.click_pos.y - pos.y))
					) {
						destroy_win(gui);
						gui = NULL;
					}
				}

				DBG_DEBUG4("modal_dialogue", "calling check_pos_win");
				check_pos_win(&ev, true);

			} while(  ev.ev_class != EVENT_NONE  &&  dr_time()-frame_start_time < 50  );

			const uint32 delta_t = (ms_per_frame * welt->get_time_multiplier()) / 16;
			DBG_DEBUG4("modal_dialogue", "calling welt->sync_step(%u)", delta_t);
			welt->sync_step(delta_t);
			welt->display(delta_t);

			if (--sync_steps_until_step == 0) {
				DBG_DEBUG4("modal_dialogue", "calling welt->step");
				intr_disable();
				welt->step();
				intr_enable();
				sync_steps_until_step = sync_steps_per_step;
			}

			const uint32 next_frame_start_time = frame_start_time + ms_per_frame;
			const uint32 now = dr_time();

			if (now < next_frame_start_time) {
				dr_sleep(next_frame_start_time - now);
			}
		}
		clear_random_mode(MODAL_RANDOM);
	}
	else {
		gfx->set_cursor_visible(true);
		gfx->set_show_load_cursor(false);
		gfx->draw_rect(0, 0, screen.w, screen.h, gfx->palette_lookup(COL_BLACK), true);

		while (gui && win_is_open(gui) && !env_t::quit_simutrans && !quit()) {
			// do not move, do not close it!

			// check for events again after waiting
			if (quit()) {
				break;
			}
			dr_prepare_flush();
			gui->draw(win_get_pos(gui), gui->get_windowsize());
			dr_flush();

			// some backends (etc. SDL2) tend to flood the queue with events. Hence we need to process them all quickly
			uint32 frame_start_time = dr_time();
			do {
				display_poll_event(&ev);
				if (ev.ev_class == EVENT_SYSTEM) {
					if (ev.ev_code == SYSTEM_RESIZE) {
						// main window resized
						gfx->on_window_resized(ev.new_window_size);
						scr_coord pos{
							(screen.w - gui->get_windowsize().w) / 2,
							(screen.h - gui->get_windowsize().h) / 2
						};
						win_clamp_xywh_position(gui, pos, gui->get_windowsize(), true);
						wins[wins[0].gui != gui].pos = pos;

						dr_prepare_flush();
						gfx->draw_rect(0, 0, ev.new_window_size.w, ev.new_window_size.h, gfx->palette_lookup(COL_BLACK), true);
						gui->draw(win_get_pos(gui), gui->get_windowsize());
						dr_flush();
					}
					else if (ev.ev_code == SYSTEM_QUIT) {
						// no world yet => simple quit
						env_t::quit_simutrans = true;
						break;
					}
				}
				else {
					// other events
					check_pos_win(&ev,true);
				}
				// however, after 50ms we do a redraw ...
			} while (ev.ev_class != EVENT_NONE &&  dr_time() - frame_start_time < 50);

			// sleep the rest of the time
			uint32 end_time = dr_time();
			if (end_time - frame_start_time < 50) {
				dr_sleep(50 - (end_time - frame_start_time));
			}
		}
		gfx->set_show_load_cursor(true);
		dr_prepare_flush();
		gfx->draw_rect(0, 0, screen.w, screen.h, gfx->palette_lookup(COL_BLACK), true);
		dr_flush();
	}

	// just trigger not another following window => wait for button release
	if (IS_LEFTCLICK(&ev)) {
		do {
			display_poll_event(&ev);
		} while (!IS_LEFTRELEASE(&ev));
	}

	// restore autosave
	env_t::autosave = old_autosave;
}
