1 /*
2  * Copyright (c) 1997 - 2003 Hansj�rg Malthaner
3  *
4  * This file is part of the Simutrans project under the artistic license.
5  * (see license.txt)
6  */
7 
8 /*
9  * Sub-window for Sim
10  * keine Klasse, da die funktionen von C-Code aus aufgerufen werden koennen
11  *
12  * The function implements a WindowManager 'Object'
13  * There's only one WindowManager
14  *
15  * 17.11.97, Hj. Malthaner
16  *
17  * Window now typified
18  * 21.06.98, Hj. Malthaner
19  */
20 
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 
25 #include "../simcolor.h"
26 #include "../simevent.h"
27 #include "../display/simgraph.h"
28 #include "../display/viewport.h"
29 #include "../simmenu.h"
30 #include "../simskin.h"
31 #include "../simsys.h"
32 #include "../simticker.h"
33 #include "simwin.h"
34 #include "../simintr.h"
35 #include "../simhalt.h"
36 #include "../simworld.h"
37 
38 #include "../dataobj/translator.h"
39 #include "../dataobj/environment.h"
40 #include "../dataobj/loadsave.h"
41 #include "../dataobj/tabfile.h"
42 
43 #include "../descriptor/skin_desc.h"
44 
45 #include "../obj/zeiger.h"
46 
47 
48 #include "../player/simplay.h"
49 #include "../tpl/inthashtable_tpl.h"
50 #include "../tpl/vector_tpl.h"
51 #include "../utils/simstring.h"
52 #include "../utils/cbuffer_t.h"
53 
54 #include "map_frame.h"
55 #include "help_frame.h"
56 #include "messagebox.h"
57 #include "gui_frame.h"
58 
59 // needed to restore/save them
60 #include "tool_selector.h"
61 #include "player_frame_t.h"
62 #include "money_frame.h"
63 #include "halt_info.h"
64 #include "convoi_detail_t.h"
65 #include "convoi_info_t.h"
66 #include "schedule_gui.h"
67 #include "line_management_gui.h"
68 #include "schedule_list.h"
69 #include "city_info.h"
70 #include "message_frame_t.h"
71 #include "message_option_t.h"
72 #include "fabrik_info.h"
73 #include "themeselector.h"
74 #include "goods_frame_t.h"
75 #include "loadfont_frame.h"
76 #include "scenario_info.h"
77 #include "depot_frame.h"
78 
79 #include "../simversion.h"
80 
81 class inthashtable_tpl<ptrdiff_t,scr_coord> old_win_pos;
82 
83 // hash-table: magic number to windowsize
84 class inthashtable_tpl<ptrdiff_t, scr_size> saved_windowsizes;
85 
86 #define dragger_size 12
87 
88 // (Mathew Hounsell)
89 // I added a button to the map window to fix it's size to the best one.
90 // This struct is the flow back to the object of the refactoring.
91 class simwin_gadget_flags_t
92 {
93 public:
simwin_gadget_flags_t()94 	simwin_gadget_flags_t(  ) : title(true), close( false ), help( false ), prev( false ), size( false ), next( false ), sticky( false ), gotopos( false ) { }
95 
96 	bool title:1;
97 	bool close:1;
98 	bool help:1;
99 	bool prev:1;
100 	bool size:1;
101 	bool next:1;
102 	bool sticky:1;
103 	bool gotopos:1;
104 };
105 
106 class simwin_t
107 {
108 public:
109 	scr_coord pos;              // Window position
110 	uint32 dauer;           // How long should the window stay open?
111 	uint8 wt;               // the flags for the window type
112 	ptrdiff_t magic_number; // either magic number or this pointer (which is unique too)
113 	gui_frame_t *gui;
114 	uint16	gadget_state;	// which buttons to hilite
115 	bool sticky;            // true if window is sticky
116 	bool rollup;
117 	bool dirty;
118 
119 	simwin_gadget_flags_t flags; // (Mathew Hounsell) See Above.
120 
simwin_t()121 	simwin_t() : flags() {}
122 
123 	bool operator== (const simwin_t &) const;
124 };
125 
operator ==(const simwin_t & other) const126 bool simwin_t::operator== (const simwin_t &other) const { return gui == other.gui; }
127 
128 
129 #define MAX_WIN (64)
130 static vector_tpl<simwin_t> wins(MAX_WIN);
131 static vector_tpl<simwin_t> kill_list(MAX_WIN);
132 
133 static karte_t* wl = NULL; // Pointer to current world is set in win_set_world
134 
135 static int top_win(int win, bool keep_state );
136 static void display_win(int win);
137 
138 
139 // Hajo: tooltip data
140 static int tooltip_xpos = 0;
141 static int tooltip_ypos = 0;
142 static const char * tooltip_text = 0;
143 static const char * static_tooltip_text = 0;
144 // Knightly :	For timed tooltip with initial delay and finite visible duration.
145 //				Valid owners are required for timing. Invalid (NULL) owners disable timing.
146 static const void * tooltip_owner = 0;	// owner of the registered tooltip
147 static const void * tooltip_group = 0;	// group to which the owner belongs
148 static uint32 tooltip_register_time = 0;	// time at which a tooltip is initially registered
149 
150 static bool show_ticker=0;
151 
152 /* Hajo: if we are inside the event handler,
153  * the window handler has gui pointer as value,
154  * to defer destruction if this window
155  */
156 static void *inside_event_handling = NULL;
157 
158 // only this gui element can set a tooltip
159 static void *tooltip_element = NULL;
160 
161 static bool destroy_framed_win(simwin_t *win);
162 
163 //=========================================================================
164 // Helper Functions
165 
166 #define REVERSE_GADGETS (!env_t::window_buttons_right)
167 
168 /**
169  * Display a window gadget
170  * @author Hj. Malthaner
171  */
display_gadget_box(sint8 code,int const x,int const y,PIXVAL lighter,PIXVAL darker,bool const pushed)172 static int display_gadget_box(sint8 code,
173 			      int const x, int const y,
174 			      PIXVAL lighter, PIXVAL darker,
175 			      bool const pushed)
176 {
177 
178 	// If we have a skin, get gadget image data
179 	const image_t *img = NULL;
180 	if(  skinverwaltung_t::gadget  ) {
181 		// "x", "?", "=", "�", "�"
182 		img = skinverwaltung_t::gadget->get_image(code);
183 	}
184 
185 	if(pushed) {
186 		// mark_rect_dirty_wc(x, y, D_GADGET_WIDTH, D_TITLEBAR_HEIGHT);
187 		display_fillbox_wh_clip_rgb(x, y, D_GADGET_WIDTH, D_TITLEBAR_HEIGHT, lighter, false);
188 	}
189 
190 	// Do we have a gadget image?
191 	if(  img != NULL  ) {
192 
193 		// 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.
194 		display_color_img(img->imageid, x-img->x + D_GET_CENTER_ALIGN_OFFSET(img->w,D_GADGET_WIDTH), y, 0, false, false);
195 
196 	}
197 	else {
198 		const char *gadget_text = "#";
199 		static const char *gadget_texts[5]={ "X", "?", "=", "<", ">" };
200 
201 		if(  code <= SKIN_BUTTON_NEXT  ) {
202 			gadget_text = gadget_texts[code];
203 		}
204 		else if(  code == SKIN_GADGET_GOTOPOS  ) {
205 			gadget_text	= "*";
206 		}
207 		else if(  code == SKIN_GADGET_NOTPINNED  ) {
208 			gadget_text	= "s";
209 		}
210 		else if(  code == SKIN_GADGET_PINNED  ) {
211 			gadget_text	= "S";
212 		}
213 		display_proportional_rgb( x+4, y+4, gadget_text, ALIGN_LEFT, color_idx_to_rgb(COL_BLACK), false );
214 	}
215 
216 	int side = x+REVERSE_GADGETS*D_GADGET_WIDTH-1;
217 	display_vline_wh_clip_rgb(side+1, y+1, D_TITLEBAR_HEIGHT-2, lighter, false);
218 	display_vline_wh_clip_rgb(side,   y+1, D_TITLEBAR_HEIGHT-2, darker,  false);
219 
220 	// Hajo: return width of gadget
221 	return D_GADGET_WIDTH;
222 }
223 
224 
225 //-------------------------------------------------------------------------
226 // (Mathew Hounsell) Created
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)227 static int display_gadget_boxes(
228 	simwin_gadget_flags_t* flags,
229 	int x, int y,
230 	PIXVAL lighter,
231 	PIXVAL darker,
232 	uint16 gadget_state,
233 	bool sticky_pushed,
234 	bool goto_pushed
235 ) {
236 	int width = 0;
237 	const int k=(REVERSE_GADGETS?1:-1);
238 
239 	// Only the close and sticky gadget can be pushed.
240 	if(  flags->close  ) {
241 		width += k*display_gadget_box( SKIN_GADGET_CLOSE, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_CLOSE) );
242 	}
243 	if(  flags->size  ) {
244 		width += k*display_gadget_box( SKIN_GADGET_MINIMIZE, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_MINIMIZE) );
245 	}
246 	if(  flags->help  ) {
247 		width += k*display_gadget_box( SKIN_GADGET_HELP, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_HELP) );
248 	}
249 	if(  flags->prev  ) {
250 		width += k*display_gadget_box( SKIN_BUTTON_PREVIOUS, x + width, y, lighter, darker, gadget_state & (1<<SKIN_BUTTON_PREVIOUS) );
251 	}
252 	if(  flags->next  ) {
253 		width += k*display_gadget_box( SKIN_BUTTON_NEXT, x + width, y, lighter, darker, gadget_state & (1<<SKIN_BUTTON_NEXT) );
254 	}
255 	if(  flags->gotopos  ) {
256 		width += k*display_gadget_box( SKIN_GADGET_GOTOPOS, x + width, y, lighter, darker, goto_pushed  ||  (gadget_state & (1<<SKIN_GADGET_GOTOPOS)) );
257 	}
258 	if(  flags->sticky  ) {
259 		width += k*display_gadget_box( sticky_pushed ? SKIN_GADGET_PINNED : SKIN_GADGET_NOTPINNED, x + width, y, lighter, darker, gadget_state & (1<<SKIN_GADGET_NOTPINNED) );
260 	}
261 
262 	return abs( width );
263 }
264 
265 
decode_gadget_boxes(simwin_gadget_flags_t const * const flags,int const x,int const px)266 static sint8 decode_gadget_boxes(
267                simwin_gadget_flags_t const * const flags,
268                int const x,
269                int const px
270 ) {
271 	int offset = px-x;
272 	const int w=(REVERSE_GADGETS?-D_GADGET_WIDTH:D_GADGET_WIDTH);
273 
274 //DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","offset=%i, w=%i",offset, w );
275 
276 	// Only the close gadget can be pushed.
277 	if( flags->close ) {
278 		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
279 //DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","close" );
280 			return SKIN_GADGET_CLOSE;
281 		}
282 		offset += w;
283 	}
284 	if( flags->size ) {
285 		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
286 //DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","size" );
287 			return SKIN_GADGET_MINIMIZE;
288 		}
289 		offset += w;
290 	}
291 	if( flags->help ) {
292 		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
293 //DBG_MESSAGE("simwin_gadget_et decode_gadget_boxes()","help" );
294 			return SKIN_GADGET_HELP;
295 		}
296 		offset += w;
297 	}
298 	if( flags->prev ) {
299 		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
300 			return SKIN_BUTTON_PREVIOUS;
301 		}
302 		offset += w;
303 	}
304 	if( flags->next ) {
305 		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
306 			return SKIN_BUTTON_NEXT;
307 		}
308 		offset += w;
309 	}
310 	if( flags->gotopos ) {
311 		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
312 			return SKIN_GADGET_GOTOPOS;
313 		}
314 		offset += w;
315 	}
316 	if( flags->sticky ) {
317 		if( offset >= 0  &&  offset<D_GADGET_WIDTH  ) {
318 			return SKIN_GADGET_NOTPINNED;
319 		}
320 		offset += w;
321 	}
322 	return SKIN_GADGET_COUNT;
323 }
324 
325 //-------------------------------------------------------------------------
326 // (Mathew Hounsell) Re-factored
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)327 static void win_draw_window_title(const scr_coord pos, const scr_size size,
328 		const FLAGGED_PIXVAL title_color,
329 		const char * const text,
330 		const FLAGGED_PIXVAL text_color,
331 		const koord3d welt_pos,
332 		const uint16 gadget_state,
333 		const bool sticky,
334 		const bool goto_pushed,
335 		simwin_gadget_flags_t &flags )
336 {
337 	PUSH_CLIP_FIT(pos.x, pos.y, size.w, size.h);
338 
339 	PIXVAL lighter = display_blend_colors(title_color, color_idx_to_rgb(COL_WHITE), 25);
340 	PIXVAL darker  = display_blend_colors(title_color, color_idx_to_rgb(COL_BLACK), 25);
341 
342 	display_fillbox_wh_clip_rgb(pos.x, pos.y, size.w, D_TITLEBAR_HEIGHT, title_color, false);
343 
344 	display_fillbox_wh_clip_rgb( pos.x + 1, pos.y,                         size.w - 2, 1, lighter, false );
345 	display_fillbox_wh_clip_rgb( pos.x + 1, pos.y + D_TITLEBAR_HEIGHT - 1, size.w - 2, 1, darker,  false );
346 
347 	display_vline_wh_clip_rgb( pos.x,              pos.y, D_TITLEBAR_HEIGHT, lighter, false );
348 	display_vline_wh_clip_rgb( pos.x + size.w - 1, pos.y, D_TITLEBAR_HEIGHT, darker,  false );
349 
350 	// Draw the gadgets and then move left and draw text.
351 	flags.gotopos = (welt_pos != koord3d::invalid);
352 	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 );
353 	int titlewidth = display_proportional_clip_rgb( pos.x + (REVERSE_GADGETS?width+4:4), pos.y+(D_TITLEBAR_HEIGHT-LINEASCENT)/2, text, ALIGN_LEFT, text_color, false );
354 	if(  flags.gotopos  ) {
355 		display_proportional_clip_rgb( pos.x + (REVERSE_GADGETS?width+4:4)+titlewidth+8, pos.y+(D_TITLEBAR_HEIGHT-LINEASCENT)/2, welt_pos.get_2d().get_fullstr(), ALIGN_LEFT, text_color, false );
356 	}
357 	POP_CLIP();
358 }
359 
360 
361 //-------------------------------------------------------------------------
362 
363 /**
364  * Draw dragger widget
365  * @author Hj. Malthaner
366  */
win_draw_window_dragger(scr_coord pos,scr_size size)367 static void win_draw_window_dragger(scr_coord pos, scr_size size)
368 {
369 	pos += size;
370 	if(  skinverwaltung_t::gadget  &&  skinverwaltung_t::gadget->get_image_id(SKIN_WINDOW_RESIZE)!=IMG_EMPTY  ) {
371 		const image_t *dragger = skinverwaltung_t::gadget->get_image(SKIN_WINDOW_RESIZE);
372 		display_color_img( dragger->get_id(), pos.x-dragger->get_pic()->w, pos.y-dragger->get_pic()->h, 0, false, false);
373 	}
374 	else {
375 		for(  int x=0;  x<dragger_size;  x++  ) {
376 			display_fillbox_wh_clip_rgb( pos.x-x, pos.y-dragger_size+x, x, 1, color_idx_to_rgb((x & 1) ? COL_BLACK : MN_GREY4), true);
377 		}
378 	}
379 }
380 
381 // Functions to save & restore windowsize
382 
guess_magic_number(simwin_t * win,ptrdiff_t magic=magic_none)383 ptrdiff_t guess_magic_number(simwin_t *win, ptrdiff_t magic = magic_none)
384 {
385 	if (win) {
386 		magic = win->magic_number;
387 	}
388 	// reduce player-wise magic numbers and magic numbers derived from handles
389 	const ptrdiff_t magic_pl[] = {
390 		magic_finances_t, magic_convoi_list, magic_convoi_list_filter, magic_line_list, magic_halt_list,
391 		magic_line_management_t, magic_ai_options_t, magic_ai_selector, magic_pwd_t, magic_jump, magic_headquarter, magic_headquarter + MAX_PLAYER_COUNT,
392 		magic_none,
393 		magic_convoi_info, magic_halt_info, magic_toolbar,
394 	};
395 
396 	for (uint i=1; i<lengthof(magic_pl); i++) {
397 		if (magic_pl[i-1] == magic_none) {
398 			continue;
399 		}
400 		if (magic_pl[i-1] <= magic  &&  magic < magic_pl[i]) {
401 			return magic_pl[i-1];
402 		}
403 	}
404 	if (win == NULL) {
405 		return magic;
406 	}
407 	// try rdwr_id
408 	uint32 id = win->gui->get_rdwr_id();
409 	if (id != magic_reserved) {
410 		return id;
411 	}
412 
413 	// anything outside the magic number's ranges will be ignored
414 	if (magic <= magic_reserved  ||  magic >= magic_max) {
415 		magic = magic_none;
416 	}
417 	return magic;
418 }
419 
save_windowsize(simwin_t * win)420 void save_windowsize(simwin_t *win)
421 {
422 	ptrdiff_t magic = guess_magic_number(win);
423 	if (magic != magic_none) {
424 		saved_windowsizes.set(magic, win->gui->get_windowsize() );
425 	}
426 }
427 
get_stored_windowsize(simwin_t * win)428 scr_size get_stored_windowsize(simwin_t *win)
429 {
430 	ptrdiff_t magic = guess_magic_number(win);
431 	if (magic != magic_none) {
432 		return saved_windowsizes.get(magic);
433 	}
434 	return scr_size();
435 }
436 
rdwr_win_settings(loadsave_t * file)437 void rdwr_win_settings(loadsave_t *file)
438 {
439 	if (file->is_loading()) {
440 		saved_windowsizes.clear();
441 		sint64 magic;
442 		do {
443 			file->rdwr_longlong(magic);
444 			if (magic != magic_none) {
445 				scr_size s;
446 				file->rdwr_long(s.w);
447 				file->rdwr_long(s.h);
448 				// ignore stuff that will not be used anymore
449 				if (magic == guess_magic_number(NULL, magic)) {
450 					saved_windowsizes.put(magic, s);
451 				}
452 			}
453 		} while (magic != magic_none);
454 	}
455 	else {
456 		typedef inthashtable_tpl<ptrdiff_t, scr_size> stupid_table_t;
457 		FOR(stupid_table_t, it, saved_windowsizes) {
458 			sint64 m = it.key;
459 			file->rdwr_longlong(m);
460 			file->rdwr_long(it.value.w);
461 			file->rdwr_long(it.value.h);
462 		}
463 		sint64 m = magic_none;
464 		file->rdwr_longlong(m);
465 	}
466 }
467 //=========================================================================
468 
469 
470 
471 // returns the window (if open) otherwise zero
win_get_magic(ptrdiff_t magic)472 gui_frame_t *win_get_magic(ptrdiff_t magic)
473 {
474 	if(  magic!=-1  &&  magic!=0  ) {
475 		// there is at most one window with a positive magic number
476 		FOR( vector_tpl<simwin_t>, const& i, wins ) {
477 			if(  i.magic_number == magic  ) {
478 				// if 'special' magic number, return it
479 				return i.gui;
480 			}
481 		}
482 	}
483 	return NULL;
484 }
485 
486 
487 // sets the magic of a gui_frame_t (needed during reload of windows)
win_set_magic(gui_frame_t * gui,ptrdiff_t magic)488 bool win_set_magic( gui_frame_t *gui, ptrdiff_t magic )
489 {
490 	if(  magic!=-1  &&  magic!=0  ) {
491 		// there is at most one window with a positive magic number
492 		FOR( vector_tpl<simwin_t>, &i, wins ) {
493 			if(  i.gui == gui  ) {
494 				i.magic_number = magic;
495 				return true;
496 			}
497 		}
498 	}
499 	return false;
500 }
501 
502 
503 /**
504  * Returns top window
505  * @author prissi
506  */
win_get_top()507 gui_frame_t *win_get_top()
508 {
509 	return wins.empty() ? 0 : wins.back().gui;
510 }
511 
512 
513 /**
514  * returns the focused component of the top window
515  * @author Knightly
516  */
win_get_focus()517 gui_component_t *win_get_focus()
518 {
519 	return wins.empty() ? 0 : wins.back().gui->get_focus();
520 }
521 
522 
win_get_open_count()523 int win_get_open_count()
524 {
525 	return wins.get_count();
526 }
527 
528 
529 // brings a window to front, if open
top_win(const gui_frame_t * gui,bool keep_rollup)530 bool top_win( const gui_frame_t *gui, bool keep_rollup )
531 {
532 	for(  uint i=0;  i<wins.get_count()-1;  i++  ) {
533 		if(wins[i].gui==gui) {
534 			top_win(i,keep_rollup);
535 			return true;
536 		}
537 	}
538 	// not open
539 	return false;
540 }
541 
542 
543 /**
544  * Checks if a window is a top level window
545  * @author Hj. Malthaner
546  */
win_is_top(const gui_frame_t * ig)547 bool win_is_top(const gui_frame_t *ig)
548 {
549 	return !wins.empty() && wins.back().gui == ig;
550 }
551 
552 
553 // window functions
554 
555 
556 // save/restore all dialogues
rdwr_all_win(loadsave_t * file)557 void rdwr_all_win(loadsave_t *file)
558 {
559 	if(  file->is_version_atleast(120, 8)  ) {
560 		if(  file->is_saving()  ) {
561 			FOR(vector_tpl<simwin_t>, & i, wins) {
562 				uint32 id = i.gui->get_rdwr_id();
563 				if(  id!=magic_reserved  ) {
564 					file->rdwr_long( id );
565 					i.pos.rdwr(file);
566 					file->rdwr_byte(i.wt);
567 					file->rdwr_bool(i.sticky);
568 					file->rdwr_bool(i.rollup);
569 					i.gui->rdwr(file);
570 				}
571 			}
572 			uint32 end = magic_none;
573 			file->rdwr_long( end );
574 		}
575 		else {
576 			// restore windows
577 			while(1) {
578 				uint32 id;
579 				file->rdwr_long(id);
580 				// create the matching
581 				gui_frame_t *w = NULL;
582 				switch(magic_numbers(id)) {
583 
584 					// end of dialogues
585 					case magic_none: return;
586 
587 					// actual dialogues to restore
588 					case magic_convoi_info:    w = new convoi_info_t(); break;
589 					case magic_themes:         w = new themeselector_t(); break;
590 					case magic_halt_info:      w = new halt_info_t(); break;
591 					case magic_reliefmap:      w = new map_frame_t(); break;
592 					case magic_ki_kontroll_t:  w = new ki_kontroll_t(); break;
593 					case magic_schedule_rdwr_dummy: w = new schedule_gui_t(); break;
594 					case magic_line_schedule_rdwr_dummy: w = new line_management_gui_t(); break;
595 					case magic_city_info_t:    w = new city_info_t(); break;
596 					case magic_messageframe:   w = new message_frame_t(); break;
597 					case magic_message_options: w = new message_option_t(); break;
598 					case magic_factory_info:   w = new fabrik_info_t(); break;
599 					case magic_goodslist:      w = new goods_frame_t(); break;
600 					case magic_font:           w = new loadfont_frame_t(); break;
601 					case magic_scenario_info:  w = new scenario_info_t(); break;
602 					case magic_depot:          w = new depot_frame_t(); break;
603 
604 					default:
605 						if(  id>=magic_finances_t  &&  id<magic_finances_t+MAX_PLAYER_COUNT  ) {
606 							w = new money_frame_t( wl->get_player(id-magic_finances_t) );
607 						}
608 						else if(  id>=magic_line_management_t  &&  id<magic_line_management_t+MAX_PLAYER_COUNT  ) {
609 							w = new schedule_list_gui_t( wl->get_player(id-magic_line_management_t) );
610 						}
611 						else if(  id>=magic_toolbar  &&  id<magic_toolbar+256  ) {
612 							tool_t::toolbar_tool[id-magic_toolbar]->update(wl->get_active_player());
613 							w = tool_t::toolbar_tool[id-magic_toolbar]->get_tool_selector();
614 						}
615 						else {
616 							dbg->error( "rdwr_all_win()", "No idea how to restore magic 0x%X", id );
617 							return;
618 						}
619 				}
620 				/* sequence is now the same for all dialogues
621 				 * restore coordinates
622 				 * create window
623 				 * read state
624 				 * restore content
625 				 * restore state - gui_frame_t::rdwr() might create its own window ->> want to restore state to that window
626 				 */
627 				scr_coord p;
628 				p.rdwr(file);
629 				uint8 win_type;
630 				file->rdwr_byte( win_type );
631 				create_win( p.x, p.y, w, (wintype)win_type, id );
632 				bool sticky, rollup;
633 				file->rdwr_bool( sticky );
634 				file->rdwr_bool( rollup );
635 				// now load the window
636 				uint32 count = wins.get_count();
637 				w->rdwr( file );
638 
639 				// restore sticky / rollup status
640 				// ensure that the new status is to currently loaded window
641 				if (wins.get_count() >= count) {
642 					wins.back().sticky = sticky;
643 					wins.back().rollup = rollup;
644 				}
645 			}
646 		}
647 	}
648 }
649 
650 
create_win(gui_frame_t * const gui,wintype const wt,ptrdiff_t const magic)651 int create_win(gui_frame_t* const gui, wintype const wt, ptrdiff_t const magic)
652 {
653 	return create_win( -1, -1, gui, wt, magic);
654 }
655 
656 
create_win(int x,int y,gui_frame_t * const gui,wintype const wt,ptrdiff_t const magic)657 int create_win(int x, int y, gui_frame_t* const gui, wintype const wt, ptrdiff_t const magic)
658 {
659 	assert(gui!=NULL  &&  magic!=0);
660 
661 	if(  gui_frame_t *win = win_get_magic(magic)  ) {
662 		if(  env_t::second_open_closes_win  ) {
663 			destroy_win( win );
664 			if(  !( wt & w_do_not_delete )  ) {
665 				delete gui;
666 			}
667 		}
668 		else {
669 			top_win( win );
670 		}
671 		return -1;
672 	}
673 
674 	if(  x==-1  &&  y==-1  &&  env_t::remember_window_positions  ) {
675 		// look for window in hash table
676 		if(  scr_coord *k = old_win_pos.access(magic)  ) {
677 			x = k->x;
678 			y = k->y;
679 		}
680 	}
681 
682 	/* if there are too many handles (likely in large games)
683 	 * we search for any error/news message at the bottom of the stack and delete it
684 	 * => newer information might be more important ...
685 	 */
686 	if(  wins.get_count()==MAX_WIN  ) {
687 		// we try to remove one of the lowest news windows (magic_none)
688 		for(  uint i=0;  i<MAX_WIN;  i++  ) {
689 			if(  wins[i].magic_number == magic_none  &&  dynamic_cast<news_window *>(wins[i].gui)!=NULL  ) {
690 				destroy_win( wins[i].gui );
691 				break;
692 			}
693 		}
694 	}
695 
696 	if(  wins.get_count() < MAX_WIN  ) {
697 
698 		if (!wins.empty()) {
699 			// mark old dirty
700 			const scr_size size = wins.back().gui->get_windowsize();
701 			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
702 		}
703 
704 		wins.append( simwin_t() );
705 		simwin_t& win = wins.back();
706 
707 		sint16 const menu_height = env_t::iconsize.h;
708 
709 		// (Mathew Hounsell) Make Sure Closes Aren't Forgotten.
710 		// Must Reset as the entries and thus flags are reused
711 		win.flags.close = true;
712 		win.flags.title = gui->has_title();
713 		win.flags.help = ( gui->get_help_filename() != NULL );
714 		win.flags.prev = gui->has_prev();
715 		win.flags.next = gui->has_next();
716 		win.flags.size = gui->has_min_sizer();
717 		win.flags.sticky = gui->has_sticky();
718 		win.gui = gui;
719 
720 		// take care of time delete windows ...
721 		win.wt    = wt & w_time_delete ? w_info : wt;
722 		win.dauer = (wt&w_time_delete) ? MESG_WAIT : -1;
723 		win.magic_number = magic;
724 		win.gadget_state = 0;
725 		win.rollup = false;
726 		win.sticky = false;
727 		win.dirty = true;
728 
729 		// Hajo: Notify window to be shown
730 		assert(gui);
731 		event_t ev;
732 
733 		ev.ev_class = INFOWIN;
734 		ev.ev_code = WIN_OPEN;
735 		ev.mx = 0;
736 		ev.my = 0;
737 		ev.cx = 0;
738 		ev.cy = 0;
739 		ev.button_state = 0;
740 
741 		void *old = inside_event_handling;
742 		inside_event_handling = gui;
743 		gui->infowin_event(&ev);
744 		inside_event_handling = old;
745 
746 		// restore windowsize
747 		scr_size stored = get_stored_windowsize(&win);
748 
749 		if (stored == scr_size()) {
750 			// not stored, take current
751 			stored = gui->get_windowsize();
752 		}
753 		// use default width
754 		stored.clip_lefttop(scr_size(D_DEFAULT_WIDTH, D_DEFAULT_HEIGHT));
755 		// clip to display size
756 		stored.clip_rightbottom( scr_size(display_get_width(), display_get_height() - env_t::iconsize.h - win_get_statusbar_height() ) );
757 
758 		if (stored != gui->get_windowsize()) {
759 			// send tailored resize event
760 			scr_size delta = stored - gui->get_windowsize();
761 			event_t wev;
762 			wev.ev_class = WINDOW_RESIZE;
763 			wev.ev_code = 0;
764 			wev.mx = delta.w;
765 			wev.my = delta.h;
766 
767 			inside_event_handling = gui;
768 			gui->infowin_event(&wev);
769 			inside_event_handling = old;
770 		}
771 
772 		scr_size size = gui->get_windowsize();
773 
774 		if(x == -1) {
775 			// try to keep the toolbar below all other toolbars
776 			y = menu_height;
777 			if(wt & w_no_overlap) {
778 				for( uint32 i=0;  i<wins.get_count()-1;  i++  ) {
779 					if(wins[i].wt & w_no_overlap) {
780 						if(wins[i].pos.y>=y) {
781 							sint16 lower_y = wins[i].pos.y + wins[i].gui->get_windowsize().h;
782 							if(lower_y >= y) {
783 								y = lower_y;
784 							}
785 						}
786 					}
787 				}
788 				// right aligned
789 //				x = max( 0, display_get_width()-size.w );
790 				// but we go for left
791 				x = 0;
792 				y = min( y, display_get_height()-size.h );
793 			}
794 			else {
795 				x = min(get_mouse_x() - size.w/2, display_get_width()-size.w);
796 				y = min(get_mouse_y() - size.h-env_t::iconsize.h, display_get_height()-size.h);
797 			}
798 		}
799 		if(x<0) {
800 			x = 0;
801 		}
802 		if(y<menu_height) {
803 			y = menu_height;
804 		}
805 		win.pos = scr_coord(x,y);
806 		win.dirty = true;
807 		return wins.get_count();
808 	}
809 	else {
810 		if(  !( wt & w_do_not_delete )  ) {
811 			delete gui;
812 		}
813 		return -1;
814 	}
815 }
816 
817 
818 /* sometimes a window cannot destroyed while it is still handled;
819  * in those cases it will added to kill list and it is only destructed
820  * by this function
821  */
process_kill_list()822 static void process_kill_list()
823 {
824 	FOR(vector_tpl<simwin_t>, & i, kill_list) {
825 		wins.remove(i);
826 		destroy_framed_win(&i);
827 	}
828 	kill_list.clear();
829 }
830 
831 
832 /**
833  * Destroy a framed window
834  * @author Hj. Malthaner
835  */
destroy_framed_win(simwin_t * wins)836 static bool destroy_framed_win(simwin_t *wins)
837 {
838 	bool r = true;
839 
840 	// mark dirty
841 	const scr_size size = wins->gui->get_windowsize();
842 	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
843 
844 	// save windowsize for later
845 	save_windowsize(wins);
846 
847 	gui_frame_t* gui = wins->gui; // 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
848 	if(  gui  ) {
849 		event_t ev;
850 
851 		ev.ev_class = INFOWIN;
852 		ev.ev_code = WIN_CLOSE;
853 		ev.mx = 0;
854 		ev.my = 0;
855 		ev.cx = 0;
856 		ev.cy = 0;
857 		ev.button_state = 0;
858 
859 		void *old = inside_event_handling;
860 		inside_event_handling = gui;
861 		wins->gui->infowin_event(&ev);
862 		inside_event_handling = old;
863 	}
864 
865 	if(  (wins->wt&w_do_not_delete) == 0  ) {
866 		if(  wins->gui == gui  ) {
867 			// remove from kill list first, otherwise delete will be called again on that window
868 			for(  uint32 j = 0;  j < kill_list.get_count();  j++  ) {
869 				if(  kill_list[j].gui == gui  ) {
870 					kill_list.remove_at(j);
871 					break;
872 				}
873 			}
874 			delete gui;
875 		}
876 		else {
877 			// wins likely modified during event handling. Assume this was just a schedule window destroying itself, and top_win() therefore modifying wins.
878 			// return false to signal destroy_all_win() that wins was already modified.
879 			r = false;
880 		}
881 	}
882 	// set dirty flag to refill background
883 	if(  wl  ) {
884 		wl->set_background_dirty();
885 	}
886 	return r;
887 }
888 
889 
destroy_win(const ptrdiff_t magic)890 bool destroy_win(const ptrdiff_t magic)
891 {
892 	const gui_frame_t *gui = win_get_magic(magic);
893 	if(gui) {
894 		return destroy_win( gui );
895 	}
896 	return false;
897 }
898 
899 
destroy_win(const gui_frame_t * gui)900 bool destroy_win(const gui_frame_t *gui)
901 {
902 	if(wins.get_count() > 1  &&  wins.back().gui == gui) {
903 		// destroy topped window, top the next window before removing
904 		// do it here as top_win manipulates the win vector
905 		top_win(wins.get_count() - 2, true);
906 	}
907 
908 	for(  uint i=0;  i<wins.get_count();  i++  ) {
909 		if(wins[i].gui == gui) {
910 			if(inside_event_handling==wins[i].gui) {
911 				kill_list.append_unique(wins[i]);
912 			}
913 			else {
914 				simwin_t win = wins[i];
915 				wins.remove_at(i);
916 				if(  win.magic_number < magic_max  ) {
917 					// save last pos
918 					if(  scr_coord *k = old_win_pos.access(win.magic_number)  ) {
919 						*k = win.pos;
920 					}
921 					else {
922 						old_win_pos.put( win.magic_number, win.pos );
923 					}
924 				}
925 				destroy_framed_win(&win);
926 			}
927 			return true;
928 			break;
929 		}
930 	}
931 	return false;
932 }
933 
934 
destroy_all_win(bool destroy_sticky)935 void destroy_all_win(bool destroy_sticky)
936 {
937 	for(  sint32 curWin = 0;  curWin < (sint32)wins.get_count();  curWin++  ) {
938 		if(  destroy_sticky  ||  !wins[curWin].sticky  ) {
939 			if(  inside_event_handling == wins[curWin].gui  ) {
940 				// only add this, if not already added
941 				kill_list.append_unique(wins[curWin]);
942 				// compact the window list
943 				wins.remove_at(curWin);
944 				curWin--;
945 			}
946 			else {
947 				if(  destroy_framed_win(&wins[curWin])  ) {
948 					// compact the window list
949 					wins.remove_at(curWin);
950 					curWin--;
951 				}
952 				// else wins was already modified - assume by the schedule window closing itself during event handling
953 			}
954 		}
955 	}
956 }
957 
958 
top_win(int win,bool keep_state)959 int top_win(int win, bool keep_state )
960 {
961 	if(  (uint32)win==wins.get_count()-1  ) {
962 		return win;
963 	} // already topped
964 
965 	// mark old dirty
966 	scr_size size = wins.back().gui->get_windowsize();
967 	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
968 	wins.back().dirty = true;
969 
970 	simwin_t tmp = wins[win];
971 	wins.remove_at(win);
972 	if(  !keep_state  ) {
973 		tmp.rollup = false;	// make visible when topping
974 	}
975 	wins.append(tmp);
976 
977 	 // mark new dirty
978 	size = wins.back().gui->get_windowsize();
979 	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
980 
981 	event_t ev;
982 
983 	ev.ev_class = INFOWIN;
984 	ev.ev_code = WIN_TOP;
985 	ev.mx = 0;
986 	ev.my = 0;
987 	ev.cx = 0;
988 	ev.cy = 0;
989 	ev.button_state = 0;
990 
991 	void *old = inside_event_handling;
992 	inside_event_handling = tmp.gui;
993 	tmp.gui->infowin_event(&ev);
994 	inside_event_handling = old;
995 
996 	return wins.get_count()-1;
997 }
998 
999 
display_win(int win)1000 void display_win(int win)
1001 {
1002 	// ok, now process it
1003 	gui_frame_t *comp = wins[win].gui;
1004 	scr_size size = comp->get_windowsize();
1005 	scr_coord pos = wins[win].pos;
1006 	FLAGGED_PIXVAL title_color = (comp->get_titlecolor()&0xFFFF);
1007 	FLAGGED_PIXVAL text_color = env_t::front_window_text_color;
1008 	if(  (unsigned)win!=wins.get_count()-1  ) {
1009 		// not top => darker
1010 		title_color = display_blend_colors(title_color, color_idx_to_rgb(COL_BLACK), env_t::bottom_window_darkness);
1011 		text_color = env_t::bottom_window_text_color;
1012 	}
1013 	bool need_dragger = comp->get_resizemode() != gui_frame_t::no_resize;
1014 
1015 	// %HACK (Mathew Hounsell) So draw will know if gadget is needed.
1016 	wins[win].flags.help = ( comp->get_help_filename() != NULL );
1017 	if(  wins[win].flags.title  ) {
1018 		win_draw_window_title(wins[win].pos,
1019 				size,
1020 				title_color,
1021 				comp->get_name(),
1022 				text_color,
1023 				comp->get_weltpos(false),
1024 				wins[win].gadget_state,
1025 				wins[win].sticky,
1026 				comp->is_weltpos(),
1027 				wins[win].flags );
1028 	}
1029 	if(  wins[win].dirty  ) {
1030 		// not sure this is still a useful call
1031 		mark_rect_dirty_wc( wins[win].pos.x, wins[win].pos.y, wins[win].pos.x+size.w+1, wins[win].pos.y+2 );
1032 		wins[win].dirty = false;
1033 	}
1034 	// mark top window, if requested
1035 	if(env_t::window_frame_active  &&  (unsigned)win==wins.get_count()-1) {
1036 		const int y_off = wins[win].flags.title ? 0 : D_TITLEBAR_HEIGHT;
1037 		if(!wins[win].rollup) {
1038 			display_ddd_box_clip_rgb( wins[win].pos.x-1, wins[win].pos.y-1 + y_off, size.w+2, size.h+2 - y_off, title_color, title_color); //, wins[win].dirty | wins[win].gui->is_dirty() );
1039 		}
1040 		else {
1041 			display_ddd_box_clip_rgb( wins[win].pos.x-1, wins[win].pos.y-1 + y_off, size.w+2, D_TITLEBAR_HEIGHT + 2 - y_off, title_color, title_color); //, wins[win].dirty | wins[win].gui->is_dirty() );
1042 		}
1043 	}
1044 	if(!wins[win].rollup) {
1045 		comp->draw(wins[win].pos, size);
1046 
1047 		// draw dragger
1048 		if(need_dragger) {
1049 			win_draw_window_dragger( pos, size);
1050 		}
1051 	}
1052 }
1053 
1054 
display_all_win()1055 void display_all_win()
1056 {
1057 	// first: empty kill list
1058 	process_kill_list();
1059 
1060 	// check which window can set tooltip
1061 	const sint16 x = get_mouse_x();
1062 	const sint16 y = get_mouse_y();
1063 	tooltip_element = NULL;
1064 	for(  uint32 i = wins.get_count(); i-- != 0;  ) {
1065 		if(  (!wins[i].rollup  &&  wins[i].gui->is_hit(x-wins[i].pos.x,y-wins[i].pos.y))  ||
1066 		     (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)
1067 		) {
1068 			// tooltips are only allowed for this window
1069 			tooltip_element = wins[i].gui;
1070 			break;
1071 		}
1072 	}
1073 
1074 	// then display windows
1075 	for(  uint i=0;  i<wins.get_count();  i++  ) {
1076 		void *old_gui = inside_event_handling;
1077 		inside_event_handling = wins[i].gui;
1078 		display_win(i);
1079 		inside_event_handling = old_gui;
1080 	}
1081 }
1082 
1083 
win_rotate90(sint16 new_ysize)1084 void win_rotate90( sint16 new_ysize )
1085 {
1086 	FOR(vector_tpl<simwin_t>, const& i, wins) {
1087 		i.gui->map_rotate90(new_ysize);
1088 	}
1089 }
1090 
1091 
remove_old_win()1092 static void remove_old_win()
1093 {
1094 	// Destroy (close) old window when life time expire
1095 	for(  int i=wins.get_count()-1;  i>=0;  i=min(i,(int)wins.get_count())-1  ) {
1096 		if(wins[i].dauer > 0) {
1097 			wins[i].dauer --;
1098 			if(wins[i].dauer == 0) {
1099 				destroy_win( wins[i].gui );
1100 			}
1101 		}
1102 	}
1103 }
1104 
1105 
snap_check_distance(scr_coord_val * r,const scr_coord_val a,const scr_coord_val b)1106 static inline void snap_check_distance( scr_coord_val *r, const scr_coord_val a, const scr_coord_val b )
1107 {
1108 	if(  abs(a-b)<=env_t::window_snap_distance  ) {
1109 		*r = a;
1110 	}
1111 }
1112 
1113 
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)1114 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 )
1115 {
1116 	bool resize;
1117 	if(  from_size==to_size  &&  from_pos!=to_pos  ) { // check if we're moving
1118 		resize = false;
1119 	}
1120 	else if(  from_size!=to_size  &&  from_pos==from_pos  ) { // or resizing the window
1121 		resize = true;
1122 	}
1123 	else {
1124 		return; // or nothing to do.
1125 	}
1126 
1127 	const int wins_count = wins.get_count();
1128 
1129 	for(  int i=0;  i<=wins_count;  i++  ) {
1130 		if(  i==win  ) {
1131 			// Don't snap to self
1132 			continue;
1133 		}
1134 
1135 		scr_coord other_pos;
1136 		scr_coord other_size;
1137 
1138 		if(  i==wins_count  ) {
1139 			// Allow snap to screen edge
1140 			other_pos.x = 0;
1141 			other_pos.y = env_t::iconsize.h;
1142 			other_size.x = display_get_width();
1143 			other_size.y = display_get_height()-win_get_statusbar_height()-other_pos.y;
1144 			if(  show_ticker  ) {
1145 				other_size.y -= TICKER_HEIGHT;
1146 			}
1147 		}
1148 		else {
1149 			// Snap to other window
1150 			other_size = wins[i].gui->get_windowsize();
1151 			other_pos = wins[i].pos;
1152 			if(  wins[i].rollup  ) {
1153 				other_size.y = D_TITLEBAR_HEIGHT;
1154 			}
1155 		}
1156 
1157 		// my bottom below other top  and  my top above other bottom  ---- in same vertical band
1158 		if(  from_pos.y+from_size.y>=other_pos.y  &&  from_pos.y<=other_pos.y+other_size.y  ) {
1159 			if(  resize  ) {
1160 				// other right side and my new right side within snap
1161 				snap_check_distance( &r->x, other_pos.x+other_size.x-from_pos.x, to_size.x );  // snap right - align right sides
1162 			}
1163 			else {
1164 				// other right side and my new right side within snap
1165 				snap_check_distance( &r->x, other_pos.x+other_size.x-from_size.x, to_pos.x );  // snap right - align right sides
1166 
1167 				// other left side and my new left side within snap
1168 				snap_check_distance( &r->x, other_pos.x, to_pos.x );  // snap left - align left sides
1169 			}
1170 		}
1171 
1172 		// my new bottom below other top  and  my new top above other bottom  ---- in same vertical band
1173 		if(  resize  ) {
1174 			if(  from_pos.y+to_size.y>other_pos.y  &&  from_pos.y<other_pos.y+other_size.y  ) {
1175 				// other left side and my new right side within snap
1176 				snap_check_distance( &r->x, other_pos.x-from_pos.x, to_size.x );  // snap right - align my right to other left
1177 			}
1178 		}
1179 		else {
1180 			if(  to_pos.y+from_size.y>other_pos.y  &&  to_pos.y<other_pos.y+other_size.y  ) {
1181 				// other left side and my new right side within snap
1182 				snap_check_distance( &r->x, other_pos.x-from_size.x, to_pos.x );  // snap right - align my right to other left
1183 
1184 				// other right side and my new left within snap
1185 				snap_check_distance( &r->x, other_pos.x+other_size.x, to_pos.x );  // snap left - align my left to other right
1186 			}
1187 		}
1188 
1189 		// my right side right of other left side  and  my left side left of other right side  ---- in same horizontal band
1190 		if(  from_pos.x+from_size.x>=other_pos.x  &&  from_pos.x<=other_pos.x+other_size.x  ) {
1191 			if(  resize  ) {
1192 				// other bottom and my new bottom within snap
1193 				snap_check_distance( &r->y, other_pos.y+other_size.y-from_pos.y, to_size.y );  // snap down - align bottoms
1194 			}
1195 			else {
1196 				// other bottom and my new bottom within snap
1197 				snap_check_distance( &r->y, other_pos.y+other_size.y-from_size.y, to_pos.y );  // snap down - align bottoms
1198 
1199 				// other top and my new top within snap
1200 				snap_check_distance( &r->y, other_pos.y, to_pos.y );  // snap up - align tops
1201 			}
1202 		}
1203 
1204 		// my new right side right of other left side  and  my new left side left of other right side  ---- in same horizontal band
1205 		if (  resize  ) {
1206 			if(  from_pos.x+to_size.x>other_pos.x  &&  from_pos.x<other_pos.x+other_size.x  ) {
1207 				// other top and my new bottom within snap
1208 				snap_check_distance( &r->y, other_pos.y-from_pos.y, to_size.y );  // snap down - align my bottom to other top
1209 			}
1210 		}
1211 		else {
1212 			if(  to_pos.x+from_size.x>other_pos.x  &&  to_pos.x<other_pos.x+other_size.x  ) {
1213 				// other top and my new bottom within snap
1214 				snap_check_distance( &r->y, other_pos.y-from_size.y, to_pos.y );  // snap down - align my bottom to other top
1215 
1216 				// other bottom and my new top within snap
1217 				snap_check_distance( &r->y, other_pos.y+other_size.y, to_pos.y );  // snap up - align my top to other bottom
1218 			}
1219 		}
1220 	}
1221 }
1222 
1223 
move_win(int win,event_t * ev)1224 void move_win(int win, event_t *ev)
1225 {
1226 	const scr_coord mouse_from( ev->cx, ev->cy );
1227 	const scr_coord mouse_to( ev->mx, ev->my );
1228 
1229 	const scr_coord from_pos = wins[win].pos;
1230 	scr_coord from_size = scr_coord(wins[win].gui->get_windowsize().w,wins[win].gui->get_windowsize().h);
1231 	if(  wins[win].rollup  ) {
1232 		from_size.y = D_TITLEBAR_HEIGHT + 2;
1233 	}
1234 
1235 	scr_coord to_pos = wins[win].pos+(mouse_to-mouse_from);
1236 	const scr_coord to_size = from_size;
1237 
1238 	if(  env_t::window_snap_distance>0  ) {
1239 		snap_check_win( win, &to_pos, from_pos, from_size, to_pos, to_size );
1240 	}
1241 
1242 	// CLIP(wert,min,max)
1243 	to_pos.x = CLIP( to_pos.x, 8-to_size.x, display_get_width()-16 );
1244 	to_pos.y = CLIP( to_pos.y, env_t::iconsize.h, display_get_height() - D_TITLEBAR_HEIGHT - win_get_statusbar_height() - TICKER_HEIGHT);
1245 
1246 	// delta is actual window movement.
1247 	const scr_coord delta = to_pos - from_pos;
1248 
1249 	wins[win].pos += delta;
1250 	// need to mark all of old and new positions dirty. -1, +2 for env_t::window_frame_active
1251 	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 );
1252 	wins[win].dirty = true;
1253 	// set dirty flag to refill background
1254 	if(wl) {
1255 		wl->set_background_dirty();
1256 	}
1257 
1258 	change_drag_start( delta.x, delta.y );
1259 }
1260 
1261 
resize_win(int win,event_t * ev)1262 void resize_win(int win, event_t *ev)
1263 {
1264 	event_t wev = *ev;
1265 	wev.ev_class = WINDOW_RESIZE;
1266 	wev.ev_code = 0;
1267 
1268 	const scr_coord mouse_from( wev.cx, wev.cy );
1269 	const scr_coord mouse_to( wev.mx, wev.my );
1270 
1271 	const scr_coord from_pos = wins[win].pos;
1272 	const scr_coord from_size = scr_coord(wins[win].gui->get_windowsize().w,wins[win].gui->get_windowsize().h);
1273 
1274 	const scr_coord to_pos = from_pos;
1275 	scr_coord to_size = from_size+(mouse_to-mouse_from);
1276 
1277 	if(  env_t::window_snap_distance>0  ) {
1278 		snap_check_win( win, &to_size, from_pos, from_size, to_pos, to_size );
1279 	}
1280 
1281 	// since we may be smaller afterwards
1282 	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
1283 	// set dirty flag to refill background
1284 	if(wl) {
1285 		wl->set_background_dirty();
1286 	}
1287 
1288 	// adjust event mouse scr_coord per snap
1289 	wev.mx = wev.cx + to_size.x - from_size.x;
1290 	wev.my = wev.cy + to_size.y - from_size.y;
1291 
1292 	wins[win].gui->infowin_event( &wev );
1293 }
1294 
1295 
1296 // returns true, if gui is a open window handle
win_is_open(gui_frame_t * gui)1297 bool win_is_open(gui_frame_t *gui)
1298 {
1299 	FOR(vector_tpl<simwin_t>, const& i, wins) {
1300 		if (i.gui == gui) {
1301 			FOR(vector_tpl<simwin_t>, const& j, kill_list) {
1302 				if (j.gui == gui) {
1303 					return false;
1304 				}
1305 			}
1306 			return true;
1307 		}
1308 	}
1309 	return false;
1310 }
1311 
1312 
win_get_pos(gui_frame_t const * const gui)1313 scr_coord const& win_get_pos(gui_frame_t const* const gui)
1314 {
1315 	for(  uint32 i = wins.get_count(); i-- != 0;  ) {
1316 		if(  wins[i].gui == gui  ) {
1317 			return wins[i].pos;
1318 		}
1319 	}
1320 	static scr_coord const bad(-1, -1);
1321 	return bad;
1322 }
1323 
1324 
win_set_pos(gui_frame_t * gui,int x,int y)1325 void win_set_pos(gui_frame_t *gui, int x, int y)
1326 {
1327 	for(  uint32 i = wins.get_count(); i-- != 0;  ) {
1328 		if(  wins[i].gui == gui  ) {
1329 			wins[i].pos.x = x;
1330 			wins[i].pos.y = y;
1331 			wins[i].dirty = true;
1332 			return;
1333 		}
1334 	}
1335 }
1336 
1337 
1338 /* main window event handler
1339  * renovated may 2005 by prissi to take care of irregularly shaped windows
1340  * also remove some unnecessary calls
1341  */
check_pos_win(event_t * ev)1342 bool check_pos_win(event_t *ev)
1343 {
1344 	static int is_resizing = -1;
1345 	static int is_moving = -1;
1346 
1347 	bool swallowed = false;
1348 
1349 	const int x = ev->ev_class==EVENT_MOVE ? ev->mx : ev->cx;
1350 	const int y = ev->ev_class==EVENT_MOVE ? ev->my : ev->cy;
1351 
1352 	// for the moment, no none events
1353 	if (ev->ev_class == EVENT_NONE) {
1354 		process_kill_list();
1355 		return false;
1356 	}
1357 
1358 	// we stop resizing once the user releases the button
1359 	if(  (is_resizing>=0  ||  is_moving>=0)  &&  (IS_LEFTRELEASE(ev)  ||  (ev->button_state&1)==0)  ) {
1360 		is_resizing = -1;
1361 		is_moving = -1;
1362 		if(  IS_LEFTRELEASE(ev)  ) {
1363 			// Knightly :	should not proceed, otherwise the left release event will be fed to other components;
1364 			//				return true (i.e. event swallowed) to prevent propagation back to the main view
1365 			return true;
1366 		}
1367 	}
1368 
1369 	// Knightly : disable any active tooltip upon mouse click by forcing expiration of tooltip duration
1370 	if(  ev->ev_class==EVENT_CLICK  ) {
1371 		tooltip_register_time = 0;
1372 	}
1373 
1374 	// click in main menu?
1375 	if (!tool_t::toolbar_tool.empty()  &&
1376 			tool_t::toolbar_tool[0]->get_tool_selector()  &&
1377 			env_t::iconsize.h > y  &&
1378 			ev->ev_class != EVENT_KEYBOARD) {
1379 		event_t wev = *ev;
1380 		inside_event_handling = tool_t::toolbar_tool[0];
1381 		tool_t::toolbar_tool[0]->get_tool_selector()->infowin_event( &wev );
1382 		inside_event_handling = NULL;
1383 		// swallow event
1384 		return true;
1385 	}
1386 
1387 	// cursor event only go to top window (but not if rolled up)
1388 	if(  (ev->ev_class == EVENT_KEYBOARD  ||  ev->ev_class == EVENT_STRING)  &&  !wins.empty()  ) {
1389 		simwin_t &win  = wins.back();
1390 		if(  !win.rollup  )  {
1391 			inside_event_handling = win.gui;
1392 			swallowed = win.gui->infowin_event(ev);
1393 		}
1394 		inside_event_handling = NULL;
1395 		process_kill_list();
1396 		return swallowed;
1397 	}
1398 
1399 	// just move top window until button release
1400 	if(  is_moving>=0  &&  (unsigned)is_moving<wins.get_count()  &&  (IS_LEFTDRAG(ev)  ||  IS_LEFTREPEAT(ev))  ) {
1401 		move_win( is_moving, ev );
1402 		return true;
1403 	}
1404 
1405 	// just resize window until button release
1406 	if(  is_resizing>=0  &&  (unsigned)is_resizing<wins.get_count()  &&  (IS_LEFTDRAG(ev)  ||  IS_LEFTREPEAT(ev))  ) {
1407 		resize_win( is_resizing, ev );
1408 		return true;
1409 	}
1410 
1411 	// swallow all other events in the infobar
1412 	if(  !(ev->ev_class == EVENT_KEYBOARD  ||  ev->ev_class == EVENT_STRING)  &&  y > display_get_height()- win_get_statusbar_height()  ) {
1413 		// swallow event
1414 		return true;
1415 	}
1416 
1417 	// swallow all other events in ticker (if there)
1418 	if(  !(ev->ev_class == EVENT_KEYBOARD  ||  ev->ev_class == EVENT_STRING)  &&  show_ticker  &&  y > display_get_height()- win_get_statusbar_height() - TICKER_HEIGHT  ) {
1419 		if(  IS_LEFTCLICK(ev)  ) {
1420 			// goto infowin koordinate, if ticker is active
1421 			koord p = ticker::get_welt_pos();
1422 			if(wl->is_within_limits(p)) {
1423 				wl->get_viewport()->change_world_position(koord3d(p,wl->min_hgt(p)));
1424 			}
1425 		}
1426 		// swallow event
1427 		return true;
1428 	}
1429 
1430 	// handle all the other events
1431 	for(  int i=wins.get_count()-1;  i>=0  &&  !swallowed;  i=min(i,(int)wins.get_count())-1  ) {
1432 
1433 		if(  wins[i].gui->is_hit( x-wins[i].pos.x, y-wins[i].pos.y )  ) {
1434 
1435 			// all events in window are swallowed
1436 			swallowed = true;
1437 
1438 			inside_event_handling = wins[i].gui;
1439 
1440 			// Top window first
1441 			if(  (int)wins.get_count()-1>i  &&  IS_LEFTCLICK(ev)  &&  (!wins[i].rollup  ||  ev->cy<wins[i].pos.y+D_TITLEBAR_HEIGHT)  ) {
1442 				i = top_win(i,false);
1443 			}
1444 
1445 			// Hajo: if within title bar && window needs decoration
1446 			// Max Kielland: Use title height
1447 			if(  y<wins[i].pos.y+D_TITLEBAR_HEIGHT  &&  wins[i].flags.title  ) {
1448 				// no more moving
1449 				is_moving = -1;
1450 				wins[i].dirty = true;
1451 
1452 				// %HACK (Mathew Hounsell) So decode will know if gadget is needed.
1453 				wins[i].flags.help = ( wins[i].gui->get_help_filename() != NULL );
1454 
1455 				// Where Was It ?
1456 				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 );
1457 
1458 				if(  code < SKIN_GADGET_COUNT  ) {
1459 					if(  IS_LEFTCLICK(ev)  ) {
1460 						wins[i].gadget_state |= (1 << code);
1461 					}
1462 					else if(  IS_LEFTRELEASE(ev)  ) {
1463 						wins[i].gadget_state &= ~(1 << code);
1464 						if(  ev->my >= wins[i].pos.y  &&  ev->my < 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->mx )==code  ) {
1465 							// do whatever needs to be done
1466 							switch(  code  ) {
1467 								case SKIN_GADGET_CLOSE :
1468 									destroy_win(wins[i].gui);
1469 									break;
1470 								case SKIN_GADGET_MINIMIZE: // (Mathew Hounsell)
1471 									ev->ev_class = WINDOW_MAKE_MIN_SIZE;
1472 									ev->ev_code = 0;
1473 									wins[i].gui->infowin_event( ev );
1474 									break;
1475 								case SKIN_GADGET_HELP :
1476 									help_frame_t::open_help_on( wins[i].gui->get_help_filename() );
1477 									break;
1478 								case SKIN_BUTTON_PREVIOUS:
1479 									ev->ev_class = WINDOW_CHOOSE_NEXT;
1480 									ev->ev_code = PREV_WINDOW;  // backward
1481 									wins[i].gui->infowin_event( ev );
1482 									break;
1483 								case SKIN_BUTTON_NEXT:
1484 									ev->ev_class = WINDOW_CHOOSE_NEXT;
1485 									ev->ev_code = NEXT_WINDOW;  // forward
1486 									wins[i].gui->infowin_event( ev );
1487 									break;
1488 								case SKIN_GADGET_GOTOPOS:
1489 									{	// change position on map (or follow)
1490 										koord3d k = wins[i].gui->get_weltpos(true);
1491 										if(  k!=koord3d::invalid  ) {
1492 											wl->get_viewport()->change_world_position( k );
1493 										}
1494 									}
1495 									break;
1496 								case SKIN_GADGET_NOTPINNED:
1497 									wins[i].sticky = !wins[i].sticky;
1498 									break;
1499 							}
1500 						}
1501 					}
1502 				}
1503 				else {
1504 					// Somewhere on the titlebar
1505 					if (IS_LEFTDRAG(ev)) {
1506 						i = top_win(i,false);
1507 						move_win(i, ev);
1508 						is_moving = i;
1509 					}
1510 					if(IS_RIGHTCLICK(ev)) {
1511 						wins[i].rollup ^= 1;
1512 						gui_frame_t *gui = wins[i].gui;
1513 						scr_size size = gui->get_windowsize();
1514 						mark_rect_dirty_wc( wins[i].pos.x, wins[i].pos.y, wins[i].pos.x+size.w, wins[i].pos.y+size.h );
1515 						if(  wins[i].rollup  ) {
1516 							wl->set_background_dirty();
1517 						}
1518 					}
1519 				}
1520 
1521 				// It has been handled so stop checking.
1522 				break;
1523 
1524 			}
1525 			else {
1526 				if(!wins[i].rollup) {
1527 					// click in Window / Resize?
1528 					//11-May-02   markus weber added
1529 
1530 					scr_size size = wins[i].gui->get_windowsize();
1531 
1532 					// resizer hit ?
1533 					const bool canresize = is_resizing>=0  ||
1534 												(ev->cx > wins[i].pos.x + size.w - dragger_size  &&
1535 												 ev->cy > wins[i].pos.y + size.h - dragger_size);
1536 
1537 					if((IS_LEFTCLICK(ev)  ||  IS_LEFTDRAG(ev)  ||  IS_LEFTREPEAT(ev))  &&  canresize  &&  wins[i].gui->get_resizemode()!=gui_frame_t::no_resize) {
1538 						resize_win( i, ev );
1539 						is_resizing = i;
1540 					}
1541 					else {
1542 						is_resizing = -1;
1543 						// click in Window
1544 						event_t wev = *ev;
1545 						translate_event(&wev, -wins[i].pos.x, -wins[i].pos.y);
1546 						wins[i].gui->infowin_event( &wev );
1547 					}
1548 				}
1549 				else {
1550 					swallowed = false;
1551 				}
1552 			}
1553 			inside_event_handling = NULL;
1554 		}
1555 	}
1556 
1557 	inside_event_handling = NULL;
1558 	process_kill_list();
1559 
1560 	return swallowed;
1561 }
1562 
1563 
win_poll_event(event_t * const ev)1564 void win_poll_event(event_t* const ev)
1565 {
1566 	display_poll_event(ev);
1567 	// main window resized
1568 	if(  ev->ev_class==EVENT_SYSTEM  &&  ev->ev_code==SYSTEM_RESIZE  ) {
1569 		// main window resized
1570 		simgraph_resize( ev->size_x, ev->size_y );
1571 		ticker::redraw();
1572 		wl->set_dirty();
1573 		wl->get_viewport()->metrics_updated();
1574 		ev->ev_class = EVENT_NONE;
1575 	}
1576 	// save and reload all windows (currently only used when a new theme is applied)
1577 	if(  ev->ev_class==EVENT_SYSTEM  &&  ev->ev_code==SYSTEM_RELOAD_WINDOWS  ) {
1578 		dr_chdir( env_t::user_dir );
1579 		loadsave_t dlg;
1580 		if(  dlg.wr_open( "dlgpos.xml", loadsave_t::xml_zipped, "temp", SERVER_SAVEGAME_VER_NR )  ) {
1581 			// save all
1582 			rdwr_all_win( &dlg );
1583 			dlg.close();
1584 			destroy_all_win( true );
1585 			if(  dlg.rd_open( "dlgpos.xml" )  ) {
1586 				// and reload them ...
1587 				rdwr_all_win( &dlg );
1588 			}
1589 		}
1590 		wl->set_dirty();
1591 		ev->ev_class = EVENT_NONE;
1592 		ticker::redraw();
1593 	}
1594 	if(  ev->ev_class==EVENT_SYSTEM  &&  ev->ev_code==SYSTEM_THEME_CHANGED  ) {
1595 		// called when font is changed
1596 		ev->mx = ev->my = ev->cx = ev->cy = 0;
1597 		FOR(vector_tpl<simwin_t>, const& i, wins) {
1598 			i.gui->infowin_event(ev);
1599 		}
1600 		ev->ev_class = EVENT_NONE;
1601 		ticker::redraw();
1602 	}
1603 }
1604 
1605 
win_get_statusbar_height()1606 uint16 win_get_statusbar_height()
1607 {
1608 	return max(LINESPACE + 2, 15);
1609 }
1610 
1611 // finally updates the display
win_display_flush(double konto)1612 void win_display_flush(double konto)
1613 {
1614 	const sint16 disp_width = display_get_width();
1615 	const sint16 disp_height = display_get_height();
1616 	const sint16 menu_height = env_t::iconsize.h;
1617 
1618 	// display main menu
1619 	tool_selector_t *main_menu = tool_t::toolbar_tool[0]->get_tool_selector();
1620 	display_set_clip_wh( 0, 0, disp_width, menu_height+1 );
1621 	if(  skinverwaltung_t::toolbar_background  &&  skinverwaltung_t::toolbar_background->get_image_id(0) != IMG_EMPTY  ) {
1622 		const image_id back_img = skinverwaltung_t::toolbar_background->get_image_id(0);
1623 		scr_coord_val w = env_t::iconsize.w;
1624 		scr_rect row = scr_rect( 0, 0, disp_width, menu_height );
1625 		display_fit_img_to_width( back_img, w );
1626 		// tile it wide
1627 		while(  w <= row.w  ) {
1628 			display_color_img( back_img, row.x, row.y, 0, false, true );
1629 			row.x += w;
1630 			row.w -= w;
1631 		}
1632 		// for the rest we have to clip the rectangle
1633 		if(  row.w > 0  ) {
1634 			clip_dimension const cl = display_get_clip_wh();
1635 			display_set_clip_wh( cl.x, cl.y, max(0, min(row.get_right(), cl.xx) - cl.x), cl.h );
1636 			display_color_img( back_img, row.x, row.y, 0, false, true );
1637 			display_set_clip_wh( cl.x, cl.y, cl.w, cl.h );
1638 		}
1639 	}
1640 	else {
1641 		display_fillbox_wh_rgb( 0, 0, disp_width, menu_height, color_idx_to_rgb(MN_GREY2), false );
1642 	}
1643 	// .. extra logic to enable tooltips
1644 	tooltip_element = menu_height > get_mouse_y() ? main_menu : NULL;
1645 	void *old_inside_event_handling = inside_event_handling;
1646 	inside_event_handling = main_menu;
1647 	main_menu->draw( scr_coord(0,-D_TITLEBAR_HEIGHT), scr_size(disp_width,menu_height) );
1648 	inside_event_handling = old_inside_event_handling;
1649 
1650 	display_set_clip_wh( 0, menu_height, disp_width, disp_height-menu_height+1 );
1651 
1652 	show_ticker = false;
1653 	ticker::draw();
1654 	if (ticker::empty()) {
1655 		// set dirty background for removing ticker
1656 		if(wl) {
1657 			wl->set_background_dirty();
1658 		}
1659 	}
1660 	else {
1661 		show_ticker = true;
1662 		// need to adapt tooltip_y coordinates
1663 		tooltip_ypos = min(tooltip_ypos, disp_height-15-10-16);
1664 	}
1665 
1666 	if(  skinverwaltung_t::compass_iso  &&  env_t::compass_screen_position  ) {
1667 		display_img_aligned( skinverwaltung_t::compass_iso->get_image_id( wl->get_settings().get_rotation() ), scr_rect(4,menu_height+4,disp_width-2*4,disp_height-menu_height-15-2*4-(TICKER_HEIGHT)*show_ticker), env_t::compass_screen_position, false );
1668 	}
1669 
1670 	{
1671 		// clip windows to avoid drawing into menu, ticker, or status-bar
1672 		PUSH_CLIP(0, menu_height, display_get_width(), display_get_height() - menu_height - (TICKER_HEIGHT)*show_ticker - win_get_statusbar_height() );
1673 
1674 		display_all_win();
1675 		remove_old_win();
1676 
1677 		if(env_t::show_tooltips) {
1678 			// Hajo: check if there is a tooltip to display
1679 			if(  tooltip_text  &&  *tooltip_text  ) {
1680 				// Knightly : display tooltip when current owner is invalid or when it is within visible duration
1681 				uint32 elapsed_time;
1682 				if(  !tooltip_owner  ||  ((elapsed_time=dr_time()-tooltip_register_time)>env_t::tooltip_delay  &&  elapsed_time<=env_t::tooltip_delay+env_t::tooltip_duration)  ) {
1683 					const sint16 width = proportional_string_width(tooltip_text)+7;
1684 					display_ddd_proportional_clip(min(tooltip_xpos,disp_width-width), max(menu_height+7,tooltip_ypos), width, 0, env_t::tooltip_color, env_t::tooltip_textcolor, tooltip_text, true);
1685 					if(wl) {
1686 						wl->set_background_dirty();
1687 					}
1688 				}
1689 			}
1690 			else if(static_tooltip_text!=NULL  &&  *static_tooltip_text) {
1691 				const sint16 width = proportional_string_width(static_tooltip_text)+7;
1692 				display_ddd_proportional_clip(min(get_mouse_x()+16,disp_width-width), max(menu_height+7,get_mouse_y()-16), width, 0, env_t::tooltip_color, env_t::tooltip_textcolor, static_tooltip_text, true);
1693 				if(wl) {
1694 					wl->set_background_dirty();
1695 				}
1696 			}
1697 			// Knightly : reset owner and group if no tooltip has been registered
1698 			if(  !tooltip_text  ) {
1699 				tooltip_owner = 0;
1700 				tooltip_group = 0;
1701 			}
1702 			// Hajo : clear tooltip text to avoid sticky tooltips
1703 			tooltip_text = 0;
1704 		}
1705 
1706 		POP_CLIP();
1707 
1708 		if(!wl) {
1709 			// no infos during loading etc
1710 			return;
1711 		}
1712 	}
1713 
1714 	char const *time = tick_to_string( wl->get_ticks(), true );
1715 
1716 	// statusbar background
1717 	KOORD_VAL const status_bar_height = win_get_statusbar_height();
1718 	KOORD_VAL const status_bar_y = disp_height - status_bar_height;
1719 	KOORD_VAL const status_bar_text_y = status_bar_y + (status_bar_height - LINESPACE) / 2;
1720 	KOORD_VAL const status_bar_icon_y = status_bar_y + (status_bar_height - 15) / 2;
1721 	display_set_clip_wh( 0, 0, disp_width, disp_height );
1722 	display_fillbox_wh_rgb(0, status_bar_y - 1, disp_width, 1, SYSCOL_STATUSBAR_DIVIDER, false);
1723 	display_fillbox_wh_rgb(0, status_bar_y, disp_width, status_bar_height, SYSCOL_STATUSBAR_BACKGROUND, false);
1724 
1725 	bool tooltip_check = get_mouse_y() > status_bar_y;
1726 	if(  tooltip_check  ) {
1727 		tooltip_xpos = get_mouse_x();
1728 		tooltip_ypos = status_bar_y-10-TICKER_HEIGHT*show_ticker;
1729 	}
1730 
1731 	// season color
1732 	display_color_img( skinverwaltung_t::seasons_icons->get_image_id(wl->get_season()), 2, status_bar_icon_y, 0, false, true );
1733 	if(  tooltip_check  &&  tooltip_xpos<14  ) {
1734 		static char const* const seasons[] = { "q2", "q3", "q4", "q1" };
1735 		tooltip_text = translator::translate(seasons[wl->get_season()]);
1736 		tooltip_check = false;
1737 	}
1738 
1739 	scr_coord_val right_border = disp_width-4;
1740 
1741 	// shown if timeline game
1742 	if(  wl->use_timeline()  &&  skinverwaltung_t::timelinesymbol  ) {
1743 		right_border -= 14;
1744 		display_color_img( skinverwaltung_t::timelinesymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true );
1745 		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
1746 			tooltip_text = translator::translate("timeline");
1747 			tooltip_check = false;
1748 		}
1749 	}
1750 
1751 	// shown if connected
1752 	if(  env_t::networkmode  &&  skinverwaltung_t::networksymbol  ) {
1753 		right_border -= 14;
1754 		display_color_img( skinverwaltung_t::networksymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true );
1755 		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
1756 			tooltip_text = translator::translate("Connected with server");
1757 			tooltip_check = false;
1758 		}
1759 	}
1760 
1761 	// put pause icon
1762 	if(  wl->is_paused()  &&  skinverwaltung_t::pausesymbol  ) {
1763 		right_border -= 14;
1764 		display_color_img( skinverwaltung_t::pausesymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true );
1765 		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
1766 			tooltip_text = translator::translate("GAME PAUSED");
1767 			tooltip_check = false;
1768 		}
1769 	}
1770 
1771 	// put fast forward icon
1772 	if(  wl->is_fast_forward()  &&  skinverwaltung_t::fastforwardsymbol  ) {
1773 		right_border -= 14;
1774 		display_color_img( skinverwaltung_t::fastforwardsymbol->get_image_id(0), right_border, status_bar_icon_y, 0, false, true );
1775 		if(  tooltip_check  &&  tooltip_xpos>=right_border  ) {
1776 			tooltip_text = translator::translate("Fast forward");
1777 			tooltip_check = false;
1778 		}
1779 	}
1780 
1781 	koord3d pos = wl->get_zeiger()->get_pos();
1782 
1783 	static cbuffer_t info;
1784 	info.clear();
1785 	if(  pos!=koord3d::invalid  ) {
1786 		info.printf( "(%s)", pos.get_str() );
1787 	}
1788 	if(  skinverwaltung_t::timelinesymbol==NULL  ) {
1789 		info.printf( " %s", translator::translate(wl->use_timeline()?"timeline":"no timeline") );
1790 	}
1791 	if(wl->show_distance!=koord3d::invalid  &&  wl->show_distance!=pos) {
1792 		info.printf("-(%d,%d)", wl->show_distance.x-pos.x, wl->show_distance.y-pos.y );
1793 	}
1794 	if(  !env_t::networkmode  ) {
1795 		// time multiplier text
1796 		if(wl->is_fast_forward()) {
1797 			info.printf(" %s(T~%1.2f)", skinverwaltung_t::fastforwardsymbol?"":">> ", wl->get_simloops()/50.0 );
1798 		}
1799 		else if(!wl->is_paused()) {
1800 			info.printf(" (T=%1.2f)", wl->get_time_multiplier()/16.0 );
1801 		}
1802 		else if(  skinverwaltung_t::pausesymbol==NULL  ) {
1803 			info.printf( " %s", translator::translate("GAME PAUSED") );
1804 		}
1805 	}
1806 #ifdef DEBUG
1807 	if(  env_t::verbose_debug>3  ) {
1808 		if(  haltestelle_t::get_rerouting_status()==RECONNECTING  ) {
1809 			info.append( " +" );
1810 		}
1811 		else if(  haltestelle_t::get_rerouting_status()==REROUTING  ) {
1812 			info.append( " *" );
1813 		}
1814 		if(  skinverwaltung_t::compass_iso == NULL  &&  wl->get_settings().get_rotation()  ) {
1815 			static const char *compass_dir[4] = { "North", "East", "South", "West" };
1816 			info.append( " " );
1817 			info.append( translator::translate( compass_dir[ 4-wl->get_settings().get_rotation() ] ) );
1818 		}
1819 	}
1820 #endif
1821 	scr_coord_val w_left = 20+display_proportional_rgb(20, status_bar_text_y, time, ALIGN_LEFT, SYSCOL_STATUSBAR_TEXT, true);
1822 	scr_coord_val w_right  = display_proportional_rgb(right_border-4, status_bar_text_y, info, ALIGN_RIGHT, SYSCOL_STATUSBAR_TEXT, true);
1823 	scr_coord_val middle = (disp_width+((w_left+8)&0xFFF0)-((w_right+8)&0xFFF0))/2;
1824 
1825 	if(wl->get_active_player()) {
1826 		char buffer[256];
1827 		display_proportional_rgb( middle-5, status_bar_text_y, wl->get_active_player()->get_name(), ALIGN_RIGHT, PLAYER_FLAG|color_idx_to_rgb(wl->get_active_player()->get_player_color1()+0), true);
1828 		money_to_string(buffer, konto );
1829 		display_proportional_rgb( middle+5, status_bar_text_y, buffer, ALIGN_LEFT, konto >= 0.0?MONEY_PLUS:MONEY_MINUS, true);
1830 	}
1831 }
1832 
1833 
win_set_world(karte_t * world)1834 void win_set_world(karte_t *world)
1835 {
1836 	wl = world;
1837 	// remove all save window positions
1838 	old_win_pos.clear();
1839 }
1840 
1841 
win_redraw_world()1842 void win_redraw_world()
1843 {
1844 	if(wl) {
1845 		wl->set_dirty();
1846 	}
1847 }
1848 
1849 
win_change_zoom_factor(bool magnify)1850 bool win_change_zoom_factor(bool magnify)
1851 {
1852 	const bool result = magnify ? zoom_factor_up() : zoom_factor_down();
1853 
1854 	wl->get_viewport()->metrics_updated();
1855 
1856 	return result;
1857 }
1858 
1859 
win_load_font(const char * fname,uint16 fontsize)1860 void win_load_font(const char *fname, uint16 fontsize)
1861 {
1862 	bool force_reload = fontsize != env_t::fontsize;
1863 	env_t::fontsize = fontsize;
1864 
1865 	if (display_load_font(fname, force_reload) ) {
1866 		// successfull
1867 		gui_theme_t::themes_init( env_t::default_theme, false );
1868 
1869 		event_t *ev = new event_t();
1870 		ev->ev_class = EVENT_SYSTEM;
1871 		ev->ev_code = SYSTEM_THEME_CHANGED;
1872 		queue_event( ev );
1873 	}
1874 	else {
1875 		// restore old font
1876 		display_load_font(env_t::fontname.c_str(), true);
1877 	}
1878 	win_redraw_world();
1879 }
1880 
1881 
1882 /**
1883  * Sets the tooltip to display.
1884  * Has to be called from within gui_frame_t::draw
1885  * @param owner : owner==NULL disables timing (initial delay and visible duration)
1886  * @author Hj. Malthaner, Knightly
1887  */
win_set_tooltip(int xpos,int ypos,const char * text,const void * const owner,const void * const group)1888 void win_set_tooltip(int xpos, int ypos, const char *text, const void *const owner, const void *const group)
1889 {
1890 	// check whether the right window will set the tooltip
1891 	if (inside_event_handling != tooltip_element) {
1892 		return;
1893 	}
1894 	// must be set every time as win_display_flush() will reset them
1895 	tooltip_text = text;
1896 
1897 	// update ownership if changed
1898 	if(  owner!=tooltip_owner  ) {
1899 		tooltip_owner = owner;
1900 		// update register time only if owner is valid
1901 		if(  owner  ) {
1902 			const uint32 current_time = dr_time();
1903 			if(  group  &&  group==tooltip_group  ) {
1904 				// case : same group
1905 				const uint32 elapsed_time = current_time - tooltip_register_time;
1906 				const uint32 threshold = env_t::tooltip_delay - (env_t::tooltip_delay>>2);	// 3/4 of delay
1907 				if(  elapsed_time>threshold  &&  elapsed_time<=env_t::tooltip_delay+env_t::tooltip_duration  ) {
1908 					// case : threshold was reached and duration not expired -> delay time is reduced to 1/4
1909 					tooltip_register_time = current_time - threshold;
1910 				}
1911 				else {
1912 					// case : either before threshold or duration expired
1913 					tooltip_register_time = current_time;
1914 				}
1915 			}
1916 			else {
1917 				// case : owner has no associated group or group is different -> simply reset to current time
1918 				tooltip_group = group;
1919 				tooltip_register_time = current_time;
1920 			}
1921 		}
1922 		else {
1923 			// no owner to associate with a group even if the group is valid
1924 			tooltip_group = 0;
1925 		}
1926 	}
1927 
1928 	tooltip_xpos = xpos;
1929 	tooltip_ypos = ypos;
1930 }
1931 
1932 
1933 /**
1934  * Sets the tooltip to display.
1935  * @author Hj. Malthaner
1936  */
win_set_static_tooltip(const char * text)1937 void win_set_static_tooltip(const char *text)
1938 {
1939 	static_tooltip_text = text;
1940 }
1941