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