1 /*
2  * Copyright (c) 1997 - 2001 Hansj�rg Malthaner
3  *
4  * This file is part of the Simutrans project under the artistic licence.
5  * (see licence.txt)
6  */
7 
8 /*
9  * Defines all button types: Normal (roundbox), Checkboxes (square), Arrows, Scrollbars
10  */
11 
12 #include "gui_button.h"
13 
14 #include "../../simcolor.h"
15 #include "../../display/simgraph.h"
16 #include "../../simevent.h"
17 #include "../simwin.h"
18 
19 #include "../../dataobj/translator.h"
20 
21 #include "../../simskin.h"
22 #include "../../descriptor/skin_desc.h"
23 #include "../../utils/simstring.h"
24 
25 // the following are only needed for the posbutton ...
26 #include "../../simworld.h"
27 #include "../../boden/grund.h"
28 #include "../../display/viewport.h"
29 
30 #include "../gui_frame.h"
31 
32 #define STATE_BIT (button_t::state)
33 #define AUTOMATIC_BIT (button_t::automatic)
34 
35 #define get_state_offset() (b_enabled ? pressed : 2)
36 
37 
38 karte_ptr_t button_t::welt;
39 
40 
button_t()41 button_t::button_t() :
42 	gui_component_t(true)
43 {
44 	b_no_translate = false;
45 	pressed = false;
46 	translated_tooltip = tooltip = NULL;
47 	background_color = color_idx_to_rgb(COL_WHITE);
48 	b_enabled = true;
49 
50 	// By default a box button
51 	init(box,"");
52 }
53 
54 
init(enum type type_par,const char * text_par,scr_coord pos_par,scr_size size_par)55 void button_t::init(enum type type_par, const char *text_par, scr_coord pos_par, scr_size size_par)
56 {
57 	translated_tooltip = NULL;
58 	tooltip = NULL;
59 	b_no_translate = ( type_par==posbutton );
60 
61 	set_typ(type_par);
62 	set_text(text_par);
63 	set_pos(pos_par);
64 	if(  size_par != scr_size::invalid  ) {
65 		set_size(size_par);
66 	}
67 }
68 
69 
70 // set type. this includes size for specified buttons.
set_typ(enum type t)71 void button_t::set_typ(enum type t)
72 {
73 	type = t;
74 	text_color = SYSCOL_BUTTON_TEXT;
75 	switch (type&TYPE_MASK) {
76 
77 		case square:
78 			text_color = SYSCOL_CHECKBOX_TEXT;
79 			if(  !strempty(translated_text)  ) {
80 				set_text(translated_text);
81 				set_size( scr_size( gui_theme_t::gui_checkbox_size.w + D_H_SPACE + proportional_string_width( translated_text ), max(gui_theme_t::gui_checkbox_size.h,LINESPACE)) );
82 			}
83 			else {
84 				set_size( scr_size( gui_theme_t::gui_checkbox_size.w, max(gui_theme_t::gui_checkbox_size.h,LINESPACE)) );
85 			}
86 			break;
87 
88 		case arrowleft:
89 		case repeatarrowleft:
90 			set_size( gui_theme_t::gui_arrow_left_size );
91 			break;
92 
93 		case posbutton:
94 			set_no_translate( true );
95 			set_size( gui_theme_t::gui_pos_button_size );
96 			break;
97 
98 		case arrowright:
99 		case repeatarrowright:
100 			set_size( gui_theme_t::gui_arrow_right_size );
101 			break;
102 
103 		case arrowup:
104 			set_size( gui_theme_t::gui_arrow_up_size );
105 			break;
106 
107 		case arrowdown:
108 			set_size( gui_theme_t::gui_arrow_down_size );
109 			break;
110 
111 		case box:
112 			text_color = SYSCOL_COLORED_BUTTON_TEXT;
113 			/* FALLTHROUGH */
114 		case roundbox:
115 			set_size( scr_size(gui_theme_t::gui_button_size.w, max(D_BUTTON_HEIGHT,LINESPACE)) );
116 			break;
117 
118 		default:
119 			break;
120 	}
121 	update_focusability();
122 }
123 
124 
get_max_size() const125 scr_size button_t::get_max_size() const
126 {
127 	switch(type&TYPE_MASK) {
128 		case square:
129 		case box:
130 		case roundbox:
131 			return (type & flexible) ? scr_size(scr_size::inf.w, get_min_size().h) : get_min_size();
132 
133 		default:
134 			return get_min_size();
135 	}
136 }
137 
get_min_size() const138 scr_size button_t::get_min_size() const
139 {
140 
141 	switch (type&TYPE_MASK) {
142 		case arrowleft:
143 		case repeatarrowleft:
144 			return gui_theme_t::gui_arrow_left_size;
145 
146 		case posbutton:
147 			return gui_theme_t::gui_pos_button_size;
148 
149 		case arrowright:
150 		case repeatarrowright:
151 			return gui_theme_t::gui_arrow_right_size;
152 
153 		case arrowup:
154 			return gui_theme_t::gui_arrow_up_size;
155 
156 		case arrowdown:
157 			return gui_theme_t::gui_arrow_down_size;
158 
159 		case square: {
160 			scr_coord_val w = translated_text ?  D_H_SPACE + proportional_string_width( translated_text ) : 0;
161 			return scr_size(w + gui_theme_t::gui_checkbox_size.w, max(gui_theme_t::gui_checkbox_size.h,LINESPACE));
162 		}
163 		case box:
164 		case roundbox: {
165 			scr_coord_val w = translated_text ?  2*D_H_SPACE + proportional_string_width( translated_text ) : 0;
166 			scr_size size(gui_theme_t::gui_button_size.w, max(D_BUTTON_HEIGHT,LINESPACE));
167 			size.w = max(size.w, w);
168 			return size;
169 		}
170 		default:
171 			return gui_component_t::get_min_size();
172 	}
173 }
174 
175 /**
176  * Sets the text displayed in the button
177  * @author Hj. Malthaner
178  */
set_text(const char * text)179 void button_t::set_text(const char * text)
180 {
181 	this->text = text;
182 	translated_text = b_no_translate ? text : translator::translate(text);
183 
184 	if(  (type & TYPE_MASK) == square  &&  !strempty(translated_text)  ) {
185 		set_size( scr_size( gui_theme_t::gui_checkbox_size.w + D_H_SPACE + proportional_string_width( translated_text ), max(gui_theme_t::gui_checkbox_size.h, LINESPACE)) );
186 	}
187 }
188 
189 
190 /**
191  * Sets the tooltip of this button
192  * @author Hj. Malthaner
193  */
set_tooltip(const char * t)194 void button_t::set_tooltip(const char * t)
195 {
196 	if(  t == NULL  ) {
197 		translated_tooltip = tooltip = NULL;
198 	}
199 	else {
200 		tooltip = t;
201 		translated_tooltip = b_no_translate ? tooltip : translator::translate(tooltip);
202 	}
203 }
204 
205 
getroffen(int x,int y)206 bool button_t::getroffen(int x,int y)
207 {
208 	bool hit=gui_component_t::getroffen(x, y);
209 	if(  pressed  &&  !hit  &&  ( (type & STATE_BIT) == 0)  ) {
210 		// moved away
211 		pressed = 0;
212 	}
213 	return hit;
214 }
215 
216 
217 /**
218  * Event responder
219  * @author Hj. Malthaner
220  */
infowin_event(const event_t * ev)221 bool button_t::infowin_event(const event_t *ev)
222 {
223 	if(  ev->ev_class==INFOWIN  &&  ev->ev_code==WIN_OPEN  ) {
224 		if(text) {
225 			translated_text = b_no_translate ? text : translator::translate(text);
226 		}
227 		if(tooltip) {
228 			translated_tooltip = b_no_translate ? tooltip : translator::translate(tooltip);
229 		}
230 	}
231 
232 	if(  ev->ev_class==EVENT_KEYBOARD  ) {
233 		if(  ev->ev_code==32  &&  get_focus()  ) {
234 			// space toggles button
235 			call_listeners( (long)0 );
236 			return true;
237 		}
238 		return false;
239 	}
240 
241 	// Hajo: we ignore resize events, they shouldn't make us
242 	// pressed or unpressed
243 	if(!b_enabled  ||  IS_WINDOW_RESIZE(ev)) {
244 		return false;
245 	}
246 
247 	// Knightly : check if the initial click and the current mouse positions are within the button's boundary
248 	bool const cxy_within_boundary = 0 <= ev->cx && ev->cx < get_size().w && 0 <= ev->cy && ev->cy < get_size().h;
249 	bool const mxy_within_boundary = 0 <= ev->mx && ev->mx < get_size().w && 0 <= ev->my && ev->my < get_size().h;
250 
251 	// Knightly : update the button pressed state only when mouse positions are within boundary or when it is mouse release
252 	if(  (type & STATE_BIT) == 0  &&  cxy_within_boundary  &&  (  mxy_within_boundary  ||  IS_LEFTRELEASE(ev)  )  ) {
253 		// Hajo: check button state, if we should look depressed
254 		pressed = (ev->button_state==1);
255 	}
256 
257 	// Knightly : make sure that the button will take effect only when the mouse positions are within the component's boundary
258 	if(  !cxy_within_boundary  ||  !mxy_within_boundary  ) {
259 		return false;
260 	}
261 
262 	if(IS_LEFTRELEASE(ev)) {
263 		if(  (type & TYPE_MASK)==posbutton  ) {
264 			koord k(targetpos.x,targetpos.y);
265 			call_listeners( &k );
266 
267 			if (type == posbutton_automatic) {
268 				welt->get_viewport()->change_world_position( koord3d(k,welt->max_hgt(k)) );
269 
270 			}
271 
272 		}
273 		else {
274 			if(  type & AUTOMATIC_BIT  ) {
275 				pressed = !pressed;
276 			}
277 
278 			call_listeners( (long)0 );
279 		}
280 	}
281 	else if(IS_LEFTREPEAT(ev)) {
282 		if((type&TYPE_MASK)>=repeatarrowleft) {
283 			call_listeners( (long)1 );
284 		}
285 	}
286 	// swallow all not handled non-keyboard events
287 	return (ev->ev_class != EVENT_KEYBOARD);
288 }
289 
290 
draw_focus_rect(scr_rect r,scr_coord_val offset)291 void button_t::draw_focus_rect( scr_rect r, scr_coord_val offset) {
292 
293 	scr_coord_val w = ((offset-1)<<1);
294 
295 	display_fillbox_wh_clip_rgb(r.x-offset+1,     r.y-1-offset+1,   r.w+w, 1, color_idx_to_rgb(COL_WHITE), false);
296 	display_fillbox_wh_clip_rgb(r.x-offset+1,     r.y+r.h+offset-1, r.w+w, 1, color_idx_to_rgb(COL_WHITE), false);
297 	display_vline_wh_clip_rgb  (r.x-offset,       r.y-offset+1,     r.h+w,    color_idx_to_rgb(COL_WHITE), false);
298 	display_vline_wh_clip_rgb  (r.x+r.w+offset-1, r.y-offset+1,     r.h+w,    color_idx_to_rgb(COL_WHITE), false);
299 }
300 
301 
302 // draw button. x,y is top left of window.
draw(scr_coord offset)303 void button_t::draw(scr_coord offset)
304 {
305 	if(  !is_visible()  ) {
306 		return;
307 	}
308 
309 	const scr_rect area( offset+pos, size );
310 	PIXVAL text_color = pressed ? SYSCOL_BUTTON_TEXT_SELECTED : this->text_color;
311 	text_color = b_enabled ? text_color : SYSCOL_BUTTON_TEXT_DISABLED;
312 
313 	switch (type&TYPE_MASK) {
314 
315 		case box: // Colored background box
316 			{
317 				display_img_stretch( gui_theme_t::button_tiles[get_state_offset()], area );
318 				display_img_stretch_blend( gui_theme_t::button_color_tiles[b_enabled && pressed], area, background_color | TRANSPARENT75_FLAG | OUTLINE_FLAG );
319 				if(  text  ) {
320 					text_color = pressed ? SYSCOL_COLORED_BUTTON_TEXT_SELECTED : text_color;
321 					// move the text to leave evt. space for a colored box top left or bottom right of it
322 					scr_rect area_text = area - gui_theme_t::gui_color_button_text_offset_right;
323 					area_text.set_pos( gui_theme_t::gui_color_button_text_offset + area.get_pos() );
324 					display_proportional_ellipsis_rgb( area_text, translated_text, ALIGN_CENTER_H | ALIGN_CENTER_V | DT_CLIP, text_color, true );
325 				}
326 				if(  win_get_focus()==this  ) {
327 					draw_focus_rect( area );
328 				}
329 			}
330 			break;
331 
332 		case roundbox: // button with inside text
333 			{
334 				display_img_stretch( gui_theme_t::round_button_tiles[get_state_offset()], area );
335 				if(  text  ) {
336 					// move the text to leave evt. space for a colored box top left or bottom right of it
337 					scr_rect area_text = area - gui_theme_t::gui_button_text_offset_right;
338 					area_text.set_pos( gui_theme_t::gui_button_text_offset + area.get_pos() );
339 					display_proportional_ellipsis_rgb( area_text, translated_text, ALIGN_CENTER_H | ALIGN_CENTER_V | DT_CLIP, text_color, true );
340 				}
341 				if(  win_get_focus()==this  ) {
342 					draw_focus_rect( area );
343 				}
344 			}
345 			break;
346 
347 		case square: // checkbox with text
348 			{
349 				display_img_aligned( gui_theme_t::check_button_img[ get_state_offset() ], area, ALIGN_CENTER_V, true );
350 				if(  text  ) {
351 					text_color = b_enabled ? this->text_color : SYSCOL_CHECKBOX_TEXT_DISABLED;
352 					scr_rect area_text = area;
353 					area_text.x += gui_theme_t::gui_checkbox_size.w + D_H_SPACE;
354 					area_text.w -= gui_theme_t::gui_checkbox_size.w + D_H_SPACE;
355 					display_proportional_ellipsis_rgb( area_text, translated_text, ALIGN_LEFT | ALIGN_CENTER_V | DT_CLIP, text_color, true );
356 				}
357 				if(  win_get_focus() == this  ) {
358 					draw_focus_rect( scr_rect( area.get_pos()+scr_coord(0,(area.get_size().h-gui_theme_t::gui_checkbox_size.w)/2), gui_theme_t::gui_checkbox_size ) );
359 				}
360 			}
361 			break;
362 
363 		case posbutton:
364 			{
365 				uint8 offset = get_state_offset();
366 				if(  offset == 0  ) {
367 					if(  grund_t *gr = welt->lookup_kartenboden(targetpos.x,targetpos.y)  ) {
368 						offset = welt->get_viewport()->is_on_center( gr->get_pos() );
369 					}
370 				}
371 				display_img_aligned( gui_theme_t::pos_button_img[ offset ], area, ALIGN_CENTER_H|ALIGN_CENTER_V, true );
372 			}
373 			break;
374 
375 		case arrowleft:
376 		case repeatarrowleft:
377 			display_img_aligned( gui_theme_t::arrow_button_left_img[ get_state_offset() ], area, ALIGN_CENTER_H|ALIGN_CENTER_V, true );
378 			break;
379 
380 		case arrowright:
381 		case repeatarrowright:
382 			display_img_aligned( gui_theme_t::arrow_button_right_img[ get_state_offset() ], area, ALIGN_CENTER_H|ALIGN_CENTER_V, true );
383 			break;
384 
385 		case arrowup:
386 			display_img_aligned( gui_theme_t::arrow_button_up_img[ get_state_offset() ], area, ALIGN_CENTER_H|ALIGN_CENTER_V, true );
387 			break;
388 
389 		case arrowdown:
390 			display_img_aligned( gui_theme_t::arrow_button_down_img[ get_state_offset() ], area, ALIGN_CENTER_H|ALIGN_CENTER_V, true );
391 			break;
392 
393 		default: ;
394 	}
395 
396 	if(  translated_tooltip  &&  getroffen( get_mouse_x()-offset.x, get_mouse_y()-offset.y )  ) {
397 		win_set_tooltip( get_mouse_x() + TOOLTIP_MOUSE_OFFSET_X, area.get_bottom() + TOOLTIP_MOUSE_OFFSET_Y, translated_tooltip, this);
398 	}
399 }
400 
401 
update_focusability()402 void button_t::update_focusability()
403 {
404 	switch (type&TYPE_MASK) {
405 
406 		case box:      // Old, 4-line box
407 		case roundbox: // New box with round corners
408 		case square:   // Little square in front of text (checkbox)
409 			set_focusable(true);
410 			break;
411 
412 		// those cannot receive focus ...
413 		case arrowleft:
414 		case repeatarrowleft:
415 		case arrowright:
416 		case repeatarrowright:
417 		case posbutton:
418 		case arrowup:
419 		case arrowdown:
420 		default:
421 			set_focusable(false);
422 			break;
423 	}
424 }
425