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