1 /*
2 * See Licensing and Copyright notice in naev.h
3 */
4
5 /**
6 * @file tabwin.c
7 *
8 * @brief Tabbed window widget.
9 */
10
11
12 #include "tk/toolkit_priv.h"
13
14 #include <stdlib.h>
15 #include "nstring.h"
16
17 #include "toolkit.h"
18 #include "font.h"
19 #include "../../input.h" /* Hack for now. */
20
21
22 #define TAB_HEIGHT 30
23
24
25 /*
26 * Prototypes.
27 */
28 static void tab_expose( Widget *tab, int exposed );
29 static int tab_mouse( Widget* tab, SDL_Event *event );
30 static int tab_key( Widget* tab, SDL_Event *event );
31 static int tab_raw( Widget* tab, SDL_Event *event );
32 static int tab_scroll( Widget *tab, int dir );
33 static void tab_render( Widget* tab, double bx, double by );
34 static void tab_renderOverlay( Widget* tab, double bx, double by );
35 static void tab_cleanup( Widget* tab );
36 static Widget *tab_getWgt( unsigned int wid, const char *tab );
37
38
39 /**
40 * @brief Creates a widget that hijacks a window and creates many children window.
41 *
42 * Position origin is 0,0 at bottom left. If you use negative X or Y
43 * positions. They actually count from the opposite side in.
44 *
45 * @param wid ID of the window to add the widget to.
46 * @param x X position within the window to use.
47 * @param y Y position within the window to use.
48 * @param w Width of the widget.
49 * @param h Height of the widget.
50 * @param name Name of the widget to use internally.
51 * @param ntabs Number of tabs in the widget.
52 * @param tabnames Name of the tabs in the widget.
53 * @param tabpos Position to set up the tabs at.
54 * @return List of created windows.
55 */
window_addTabbedWindow(const unsigned int wid,const int x,const int y,const int w,const int h,const char * name,int ntabs,const char ** tabnames,int tabpos)56 unsigned int* window_addTabbedWindow( const unsigned int wid,
57 const int x, const int y, /* position */
58 const int w, const int h, /* size */
59 const char* name, int ntabs, const char **tabnames, int tabpos )
60 {
61 int i;
62 int wx,wy, ww,wh;
63 Window *wdw;
64 Widget *wgt;
65
66 /* Create the Widget. */
67 wdw = window_wget(wid);
68 wgt = window_newWidget(wdw, name);
69 if (wgt == NULL)
70 return NULL;
71
72 /* generic */
73 wgt->type = WIDGET_TABBEDWINDOW;
74
75 /* specific */
76 wgt_setFlag( wgt, WGT_FLAG_RAWINPUT );
77 wgt->exposeevent = tab_expose;
78 wgt->rawevent = tab_raw;
79 wgt->render = tab_render;
80 wgt->renderOverlay = tab_renderOverlay;
81 wgt->cleanup = tab_cleanup;
82 wgt->dat.tab.ntabs = ntabs;
83 wgt->dat.tab.tabpos = tabpos;
84 wgt->dat.tab.font = &gl_defFont;
85
86 /* position/size */
87 wgt->x = (double) (x<0) ? 0 : x;
88 wgt->y = (double) (y<0) ? 0 : y;
89 wgt->w = (double) (w<0) ? wdw->w : w;
90 wgt->h = (double) (h<0) ? wdw->h : h;
91
92 /* Calculate window position and size. */
93 wx = wdw->x + wgt->x;
94 wy = wdw->y + wgt->y;
95 ww = wgt->w;
96 wh = wgt->h;
97 if (tabpos == 0) {
98 wy += TAB_HEIGHT;
99 wh -= TAB_HEIGHT;
100 }
101 else if (tabpos == 1) {
102 wh -= TAB_HEIGHT;
103 }
104 else
105 WARN( "Tab position '%d' parameter does not make sense", tabpos );
106
107 /* Copy tab information. */
108 wgt->dat.tab.tabnames = malloc( sizeof(char*) * ntabs );
109 wgt->dat.tab.windows = malloc( sizeof(unsigned int) * ntabs );
110 wgt->dat.tab.namelen = malloc( sizeof(int) * ntabs );
111 for (i=0; i<ntabs; i++) {
112 /* Get name and length. */
113 wgt->dat.tab.tabnames[i] = strdup( tabnames[i] );
114 wgt->dat.tab.namelen[i] = gl_printWidthRaw( wgt->dat.tab.font,
115 wgt->dat.tab.tabnames[i] );
116 /* Create windows with flags.
117 * Parent window handles events for the children.
118 */
119 wgt->dat.tab.windows[i] = window_createFlags( tabnames[i], wx, wy, ww, wh,
120 WINDOW_NOFOCUS | WINDOW_NORENDER | WINDOW_NOINPUT | WINDOW_NOBORDER );
121 }
122
123 /* Return list of windows. */
124 return wgt->dat.tab.windows;
125 }
126
127
128 /**
129 * @brief Handles scrolling on a tabbed window's tab bar.
130 *
131 * @param tab Widget being scrolled on.
132 * @param dir Direction to scroll in.
133 * @return Index of the newly-selected tab.
134 */
tab_scroll(Widget * tab,int dir)135 static int tab_scroll( Widget *tab, int dir )
136 {
137 int new;
138
139 if (dir > 0)
140 new = (tab->dat.tab.active + 1) % tab->dat.tab.ntabs;
141 else {
142 /* Wrap manually to avoid undefined behaviour. */
143 if (tab->dat.tab.active == 0)
144 new = tab->dat.tab.ntabs - 1;
145 else
146 new = (tab->dat.tab.active - 1) % tab->dat.tab.ntabs;
147 }
148
149 return new;
150 }
151
152
153 /**
154 * @brief Handles focus restoring for a tabbed window's children.
155 *
156 * @param tab Tabbed window widget.
157 */
tab_expose(Widget * tab,int exposed)158 static void tab_expose( Widget *tab, int exposed )
159 {
160 Window *wdw;
161
162 wdw = window_wget( tab->dat.tab.windows[ tab->dat.tab.active ] );
163 if (wdw == NULL)
164 return;
165
166 /* Re-focus widgets if visible. */
167 if (exposed)
168 toolkit_focusSanitize( wdw );
169 /* Clear focus (disables text input, etc.) */
170 else
171 toolkit_focusClear( wdw );
172 }
173
174
175 /**
176 * @brief Handles input for an tabbed window widget.
177 *
178 * @param tab Tabbed Window widget to handle event.
179 * @param key Key being handled.
180 * @param mod Mods when key is being pressed.
181 * @return 1 if the event was used, 0 if it wasn't.
182 */
tab_raw(Widget * tab,SDL_Event * event)183 static int tab_raw( Widget* tab, SDL_Event *event )
184 {
185 Window *wdw;
186 int ret;
187
188 /* First handle event internally. */
189 ret = 0;
190 if (event->type == SDL_MOUSEBUTTONDOWN)
191 ret = tab_mouse( tab, event );
192 #if SDL_VERSION_ATLEAST(2,0,0)
193 else if (event->type == SDL_MOUSEWHEEL)
194 ret = tab_mouse( tab, event );
195 #endif /* SDL_VERSION_ATLEAST(2,0,0) */
196 else if (event->type == SDL_KEYDOWN)
197 ret = tab_key( tab, event );
198
199 /* Took the event. */
200 if (ret)
201 return ret;
202
203 /* Give event to window. */
204 wdw = window_wget( tab->dat.tab.windows[ tab->dat.tab.active ] );
205 if (wdw == NULL) {
206 WARN("Active window in window '%s' not found in stack.", tab->name);
207 return 0;
208 }
209
210 /* Give the active window the input. */
211 return toolkit_inputWindow( wdw, event, 0 );
212 }
213
214
215 /**
216 * @brief Handles mouse events.
217 */
tab_mouse(Widget * tab,SDL_Event * event)218 static int tab_mouse( Widget* tab, SDL_Event *event )
219 {
220 int i, p, old, change;
221 Window *parent;
222 int x, y, rx, ry;
223
224 /* Get parent window. */
225 parent = window_wget( tab->wdw );
226 if (parent == NULL)
227 return 0;
228
229 /* Convert to window space. */
230 toolkit_inputTranslateCoords( parent, event, &x, &y, &rx, &ry );
231
232 /* Translate to widget space. */
233 x -= tab->x;
234 y -= tab->y;
235
236 /* Since it's at the top we have to translate down. */
237 if (tab->dat.tab.tabpos == 1)
238 y -= (tab->h-TAB_HEIGHT);
239
240 /* Make sure event is in the TAB HEIGHT area. */
241 if ((y>=TAB_HEIGHT) || (y<0))
242 return 0;
243
244 /* Handle event. */
245 p = 20;
246 for (i=0; i<tab->dat.tab.ntabs; i++) {
247 /* Too far left, won't match any tabs. */
248 if (x < p)
249 break;
250
251 p += 10 + tab->dat.tab.namelen[i];
252
253 /* Too far right, try next tab. */
254 if (x >= p)
255 continue;
256
257 old = tab->dat.tab.active;
258
259 /* Mark as active. */
260 change = -1;
261 #if !SDL_VERSION_ATLEAST(2,0,0)
262 if (event->button.button == SDL_BUTTON_WHEELUP)
263 change = tab_scroll(tab, -1);
264 else if (event->button.button == SDL_BUTTON_WHEELDOWN)
265 change = tab_scroll(tab, 1);
266 #else /* !SDL_VERSION_ATLEAST(2,0,0) */
267 if (event->type == SDL_MOUSEWHEEL) {
268 if (event->wheel.y > 0)
269 change = tab_scroll(tab, -1);
270 else
271 change = tab_scroll(tab, 1);
272 }
273 #endif /* SDL_VERSION_ATLEAST(2,0,0) */
274 else
275 tab->dat.tab.active = i;
276
277 if (change != -1)
278 tab->dat.tab.active = change;
279
280 /* Create event. */
281 if (tab->dat.tab.onChange != NULL)
282 tab->dat.tab.onChange( tab->wdw, tab->name, old, tab->dat.tab.active );
283 break;
284 }
285
286 return 0;
287 }
288
289
290 /**
291 * @brief Handles key events.
292 */
293 #define CHECK_CHANGE(n,v) \
294 bind_key = input_getKeybind(n, NULL, &bind_mod); \
295 if ((key == bind_key) && (mod == bind_mod)) \
296 change = v
tab_key(Widget * tab,SDL_Event * event)297 static int tab_key( Widget* tab, SDL_Event *event )
298 {
299 int old, change;
300 SDLKey key, bind_key;
301 SDLMod mod, bind_mod;
302 Window *wdw;
303 int ret;
304
305 /* Event info. */
306 key = event->key.keysym.sym;
307 mod = input_translateMod( event->key.keysym.mod );
308
309 /* Handle tab changing. */
310 change = -1;
311 CHECK_CHANGE( "switchtab1", 0 );
312 CHECK_CHANGE( "switchtab2", 1 );
313 CHECK_CHANGE( "switchtab3", 2 );
314 CHECK_CHANGE( "switchtab4", 3 );
315 CHECK_CHANGE( "switchtab5", 4 );
316 CHECK_CHANGE( "switchtab6", 5 );
317 CHECK_CHANGE( "switchtab7", 6 );
318 CHECK_CHANGE( "switchtab8", 7 );
319 CHECK_CHANGE( "switchtab9", 8 );
320 CHECK_CHANGE( "switchtab0", 9 );
321
322 /* Window. */
323 ret = 0;
324 wdw = window_wget( tab->dat.tab.windows[ tab->dat.tab.active ] );
325
326 /* Handle keypresses. */
327 switch (key) {
328 case SDLK_TAB:
329 if (mod & NMOD_CTRL) {
330 if (mod & NMOD_SHIFT) {
331 /* Wrap manually to avoid undefined behaviour. */
332 if (tab->dat.tab.active == 0)
333 change = tab->dat.tab.ntabs - 1;
334 else
335 change = (tab->dat.tab.active - 1) % tab->dat.tab.ntabs;
336 }
337 else
338 change = (tab->dat.tab.active + 1) % tab->dat.tab.ntabs;
339 }
340 else {
341 /* This is entirely backwards, but it's working around existing widget placement. */
342 if (mod & NMOD_SHIFT)
343 toolkit_nextFocus( wdw );
344 else
345 toolkit_prevFocus( wdw );
346 }
347 ret = 1;
348 break;
349
350 default:
351 break;
352 }
353
354 /* Switch to the selected tab if it exists. */
355 if (change != -1) {
356 old = tab->dat.tab.active;
357 tab->dat.tab.active = change;
358 /* Create event. */
359 if (tab->dat.tab.onChange != NULL)
360 tab->dat.tab.onChange( tab->wdw, tab->name, old, tab->dat.tab.active );
361 ret = 1;
362 }
363
364 return ret;
365 }
366 #undef CHECK_CHANGE
367
368
369 /**
370 * @brief Renders a button widget.
371 *
372 * @param tab WIDGET_BUTTON widget to render.
373 * @param bx Base X position.
374 * @param by Base Y position.
375 */
tab_render(Widget * tab,double bx,double by)376 static void tab_render( Widget* tab, double bx, double by )
377 {
378 int i, x, y;
379 Window *wdw;
380 const glColour *c, *lc;
381
382 /** Get window. */
383 wdw = window_wget( tab->dat.tab.windows[ tab->dat.tab.active ] );
384 if (wdw == NULL) {
385 WARN("Active window in widget '%s' not found in stack.", tab->name);
386 return;
387 }
388
389 /* Render the active window. */
390 window_render( wdw );
391
392 /* Render tabs ontop. */
393 x = bx+tab->x+20;
394 y = by+tab->y;
395 if (tab->dat.tab.tabpos == 1)
396 y += tab->h-TAB_HEIGHT;
397 for (i=0; i<tab->dat.tab.ntabs; i++) {
398 if (i!=tab->dat.tab.active) {
399 lc = toolkit_col;
400 c = toolkit_colDark;
401
402 /* Draw border. */
403 toolkit_drawRect( x, y, tab->dat.tab.namelen[i] + 10,
404 TAB_HEIGHT, lc, c );
405 toolkit_drawOutline( x+1, y+1, tab->dat.tab.namelen[i] + 8,
406 TAB_HEIGHT-1, 1., c, &cBlack );
407 }
408 else {
409 if (i==0)
410 toolkit_drawRect( x-1, y+0,
411 1, TAB_HEIGHT+1, toolkit_colDark, &cGrey20 );
412 else if (i==tab->dat.tab.ntabs-1)
413 toolkit_drawRect( x+tab->dat.tab.namelen[i]+9, y+0,
414 1, TAB_HEIGHT+1, toolkit_colDark, &cGrey20 );
415 }
416 /* Draw text. */
417 gl_printRaw( tab->dat.tab.font, x + 5,
418 y + (TAB_HEIGHT-tab->dat.tab.font->h)/2, &cBlack,
419 tab->dat.tab.tabnames[i] );
420
421 /* Go to next line. */
422 x += 10 + tab->dat.tab.namelen[i];
423 }
424 }
425
426
427 /**
428 * @brief Renders a button widget overlay.
429 *
430 * @param tab WIDGET_BUTTON widget to render.
431 * @param bx Base X position.
432 * @param by Base Y position.
433 */
tab_renderOverlay(Widget * tab,double bx,double by)434 static void tab_renderOverlay( Widget* tab, double bx, double by )
435 {
436 (void) bx;
437 (void) by;
438 Window *wdw;
439
440 /** Get window. */
441 wdw = window_wget( tab->dat.tab.windows[ tab->dat.tab.active ] );
442 if (wdw == NULL) {
443 WARN("Active window in widget '%s' not found in stack.", tab->name);
444 return;
445 }
446
447 /* Render overlay. */
448 window_renderOverlay( wdw );
449 }
450
451
452 /**
453 * @brief Clean up function for the button widget.
454 *
455 * @param tab Tabbed Window to clean up.
456 */
tab_cleanup(Widget * tab)457 static void tab_cleanup( Widget *tab )
458 {
459 int i;
460 for (i=0; i<tab->dat.tab.ntabs; i++) {
461 free( tab->dat.tab.tabnames[i] );
462 window_destroy( tab->dat.tab.windows[i] );
463 }
464 free( tab->dat.tab.tabnames );
465 free( tab->dat.tab.windows );
466 free( tab->dat.tab.namelen );
467 }
468
469
470 /**
471 * @brief Gets the widget.
472 */
tab_getWgt(unsigned int wid,const char * tab)473 static Widget *tab_getWgt( unsigned int wid, const char *tab )
474 {
475 Widget *wgt = window_getwgt( wid, tab );
476
477 /* Must be found in stack. */
478 if (wgt == NULL) {
479 WARN("Widget '%s' not found", tab);
480 return NULL;;
481 }
482
483 /* Must be an image array. */
484 if (wgt->type != WIDGET_TABBEDWINDOW) {
485 WARN("Widget '%s' is not an image array.", tab);
486 return NULL;
487 }
488
489 return wgt;
490 }
491
492
493 /**
494 * @brief Sets the active tab.
495 *
496 * @param wid Window to which tabbed window belongs.
497 * @param tab Name of the tabbed window.
498 * @param active tab to set active.
499 */
window_tabWinSetActive(const unsigned int wid,const char * tab,int active)500 int window_tabWinSetActive( const unsigned int wid, const char *tab, int active )
501 {
502 int old;
503
504 Widget *wgt = tab_getWgt( wid, tab );
505 if (wgt == NULL)
506 return -1;
507
508 old = wgt->dat.tab.active;
509
510 /* Set active window. */
511 wgt->dat.tab.active = active;
512
513 /* Create event. */
514 if (wgt->dat.tab.onChange != NULL)
515 wgt->dat.tab.onChange( wid, wgt->name, old, wgt->dat.tab.active );
516
517 return 0;
518 }
519
520
521 /**
522 * @brief Gets the active tab.
523 *
524 * @param wid Window to which tabbed window belongs.
525 * @param tab Name of the tabbed window.
526 * @return The ID of the active tab.
527 */
window_tabWinGetActive(const unsigned int wid,const char * tab)528 int window_tabWinGetActive( const unsigned int wid, const char *tab )
529 {
530 Widget *wgt = tab_getWgt( wid, tab );
531 if (wgt == NULL)
532 return -1;
533
534 /* Get active window. */
535 return wgt->dat.tab.active;
536 }
537
538
539 /**
540 * @brief Sets the onChange function callback.
541 *
542 * @param wid Window to which tabbed window belongs.
543 * @param tab Name of the tabbed window.
544 * @param onChange Callback to use (NULL disables).
545 */
window_tabWinOnChange(const unsigned int wid,const char * tab,void (* onChange)(unsigned int,char *,int,int))546 int window_tabWinOnChange( const unsigned int wid, const char *tab,
547 void(*onChange)(unsigned int,char*,int,int) )
548 {
549 Widget *wgt = tab_getWgt( wid, tab );
550 if (wgt == NULL)
551 return -1;
552
553 /* Set on change function. */
554 wgt->dat.tab.onChange = onChange;
555
556 return 0;
557 }
558
559
560 /**
561 * @brief Changes the font used by a tabbed window widget.
562 *
563 * @param wid Window to which tabbed window belongs.
564 * @param tab Name of the tabbed window.
565 * @param font Font to set to.
566 * @return 0 on success.
567 */
window_tabSetFont(const unsigned int wid,const char * tab,const glFont * font)568 int window_tabSetFont( const unsigned int wid, const char *tab, const glFont *font )
569 {
570 int i;
571 Widget *wgt = tab_getWgt( wid, tab );
572 if (wgt == NULL)
573 return -1;
574
575 wgt->dat.tab.font = font;
576 for (i=0; i<wgt->dat.tab.ntabs; i++)
577 wgt->dat.tab.namelen[i] = gl_printWidthRaw( wgt->dat.tab.font,
578 wgt->dat.tab.tabnames[i] );
579
580 return 0;
581 }
582
583
584 /**
585 * @brief Gets the tab windows children windows.
586 *
587 * @param wid Window to which tabbed window belongs.
588 * @param tab Name of the tabbed window.
589 * @return The children windows.
590 */
window_tabWinGet(const unsigned int wid,const char * tab)591 unsigned int* window_tabWinGet( const unsigned int wid, const char *tab )
592 {
593 Widget *wgt = tab_getWgt( wid, tab );
594 if (wgt == NULL)
595 return NULL;
596 return wgt->dat.tab.windows;
597 }
598
599 /**
600 * @brief Gets the total width of all tabs in a tabbed window.
601 *
602 * @param wid Window to which tabbed window belongs.
603 * @param tab Name of the tabbed window.
604 * @return Bar width in pixels
605 */
window_tabWinGetBarWidth(const unsigned int wid,const char * tab)606 int window_tabWinGetBarWidth( const unsigned int wid, const char* tab )
607 {
608 int i, w;
609
610 Widget *wgt = tab_getWgt( wid, tab );
611 if (wgt == NULL)
612 return 0;
613
614 w = 20;
615 for (i=0; i<wgt->dat.tab.ntabs; i++)
616 w += 10 + wgt->dat.tab.namelen[i];
617
618 return w;
619 }
620