minimenu; added special handling for leftmost tab (Sel/Unselected versions) .. so...
[pandora-libraries.git] / minimenu / mmui.c
1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <limits.h>
5 #include <time.h>
6 #include <unistd.h>
7 #include "SDL.h"
8 #include "SDL_audio.h"
9 #include "SDL_image.h"
10 #include "SDL_ttf.h"
11 #include "SDL_gfxPrimitives.h"
12 #include "SDL_rotozoom.h"
13 #include "SDL_thread.h"
14 #include <sys/types.h>
15 #include <dirent.h>
16
17 #include "pnd_conf.h"
18 #include "pnd_logger.h"
19 #include "pnd_pxml.h"
20 #include "pnd_container.h"
21 #include "pnd_discovery.h"
22 #include "pnd_apps.h"
23 #include "pnd_device.h"
24 #include "../lib/pnd_pathiter.h"
25 #include "pnd_utility.h"
26
27 #include "mmenu.h"
28 #include "mmcat.h"
29 #include "mmcache.h"
30 #include "mmui.h"
31 #include "mmwrapcmd.h"
32
33 #define CHANGED_NOTHING     (0)
34 #define CHANGED_CATEGORY    (1<<0)  /* changed to different category */
35 #define CHANGED_SELECTION   (1<<1)  /* changed app selection */
36 #define CHANGED_DATAUPDATE  (1<<2)  /* deferred preview pic or icon came in */
37 #define CHANGED_APPRELOAD   (1<<3)  /* new set of applications entirely */
38 #define CHANGED_EVERYTHING  (1<<4)  /* redraw it all! */
39 unsigned int render_mask = CHANGED_EVERYTHING;
40
41 /* SDL
42  */
43 SDL_Surface *sdl_realscreen = NULL;
44 unsigned int sdl_ticks = 0;
45 SDL_Thread *g_preview_thread = NULL;
46
47 enum { sdl_user_ticker = 0,
48        sdl_user_finishedpreview = 1,
49        sdl_user_finishedicon = 2,
50 };
51
52 /* app state
53  */
54 unsigned short int g_scale = 1; // 1 == noscale
55
56 SDL_Surface *g_imgcache [ IMG_MAX ];
57
58 TTF_Font *g_big_font = NULL;
59 TTF_Font *g_grid_font = NULL;
60 TTF_Font *g_detailtext_font = NULL;
61 TTF_Font *g_tab_font = NULL;
62
63 extern pnd_conf_handle g_conf;
64
65 /* current display state
66  */
67 int ui_rows_scrolled_down = 0;          // number of rows that should be missing from top of the display
68 mm_appref_t *ui_selected = NULL;
69 unsigned char ui_category = 0;          // current category
70 unsigned char ui_catshift = 0;          // how many cats are offscreen to the left
71
72 extern mm_category_t g_categories [ MAX_CATS ];
73 extern unsigned char g_categorycount;
74
75 static SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ); // height -1 means ignore
76 static int ui_selected_index ( void );
77
78 unsigned char ui_setup ( void ) {
79
80   /* set up SDL
81    */
82
83   SDL_Init ( SDL_INIT_EVERYTHING | SDL_INIT_NOPARACHUTE );
84
85   SDL_JoystickOpen ( 0 ); // turn on joy-0
86
87   SDL_WM_SetCaption ( "mmenu", "mmenu" );
88
89   // hide the mouse cursor if we can
90   if ( SDL_ShowCursor ( -1 ) == 1 ) {
91     SDL_ShowCursor ( 0 );
92   }
93
94   atexit ( SDL_Quit );
95
96   // open up a surface
97   unsigned int svm = SDL_SWSURFACE /*| SDL_FULLSCREEN*/ /* 0*/;
98   if ( pnd_conf_get_as_int_d ( g_conf, "display.fullscreen", 0 ) > 0 ) {
99     svm |= SDL_FULLSCREEN;
100   }
101
102   sdl_realscreen =
103     SDL_SetVideoMode ( 800 * g_scale, 480 * g_scale, 16 /*bpp*/, svm );
104
105   if ( ! sdl_realscreen ) {
106     pnd_log ( pndn_error, "ERROR: Couldn't open SDL real screen; dieing." );
107     return ( 0 );
108   }
109
110   pnd_log ( pndn_debug, "Pixel format:" );
111   pnd_log ( pndn_debug, "bpp b: %u\n", sdl_realscreen -> format -> BitsPerPixel );
112   pnd_log ( pndn_debug, "bpp B: %u\n", sdl_realscreen -> format -> BytesPerPixel );
113   pnd_log ( pndn_debug, "R mask: %08x\n", sdl_realscreen -> format -> Rmask );
114   pnd_log ( pndn_debug, "G mask: %08x\n", sdl_realscreen -> format -> Gmask );
115   pnd_log ( pndn_debug, "B mask: %08x\n", sdl_realscreen -> format -> Bmask );
116
117 #if 0 // audio
118   {
119     SDL_AudioSpec fmt;
120
121     /* Set 16-bit stereo audio at 22Khz */
122     fmt.freq = 44100; //22050;
123     fmt.format = AUDIO_S16; //AUDIO_S16;
124     fmt.channels = 1;
125     fmt.samples = 2048;        /* A good value for games */
126     fmt.callback = mixaudio;
127     fmt.userdata = NULL;
128
129     /* Open the audio device and start playing sound! */
130     if ( SDL_OpenAudio ( &fmt, NULL ) < 0 ) {
131       zotlog ( "Unable to open audio: %s\n", SDL_GetError() );
132       exit ( 1 );
133     }
134
135     SDL_PauseAudio ( 0 );
136   }
137 #endif
138
139   // images
140   //IMG_Init ( IMG_INIT_JPG | IMG_INIT_PNG );
141
142   /* fonts
143    */
144
145   // init
146   if ( TTF_Init() == -1 ) {
147     pnd_log ( pndn_error, "ERROR: Couldn't set up SDL TTF lib\n" );
148     return ( 0 ); // couldn't set up SDL TTF
149   }
150
151   char fullpath [ PATH_MAX ];
152   // big font
153   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "minimenu.font" ) );
154   g_big_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
155   if ( ! g_big_font ) {
156     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
157               pnd_conf_get_as_char ( g_conf, "minimenu.font" ), pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
158     return ( 0 ); // couldn't set up SDL TTF
159   }
160
161   // grid font
162   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "grid.font" ) );
163   g_grid_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
164   if ( ! g_grid_font ) {
165     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
166               pnd_conf_get_as_char ( g_conf, "grid.font" ), pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
167     return ( 0 ); // couldn't set up SDL TTF
168   }
169
170   // detailtext font
171   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "detailtext.font" ) );
172   g_detailtext_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
173   if ( ! g_detailtext_font ) {
174     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
175               pnd_conf_get_as_char ( g_conf, "detailtext.font" ), pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
176     return ( 0 ); // couldn't set up SDL TTF
177   }
178
179   // tab font
180   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "tabs.font" ) );
181   g_tab_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
182   if ( ! g_tab_font ) {
183     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
184               pnd_conf_get_as_char ( g_conf, "tabs.font" ), pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
185     return ( 0 ); // couldn't set up SDL TTF
186   }
187
188   return ( 1 );
189 }
190
191 mm_imgcache_t g_imagecache [ IMG_TRUEMAX ] = {
192   { IMG_BACKGROUND_800480,    "graphics.IMG_BACKGROUND_800480" },
193   { IMG_BACKGROUND_TABMASK,   "graphics.IMG_BACKGROUND_TABMASK" },
194   { IMG_DETAIL_PANEL,         "graphics.IMG_DETAIL_PANEL" },
195   { IMG_DETAIL_BG,            "graphics.IMG_DETAIL_BG" },
196   { IMG_SELECTED_ALPHAMASK,   "graphics.IMG_SELECTED_ALPHAMASK" },
197   { IMG_TAB_SEL,              "graphics.IMG_TAB_SEL" },
198   { IMG_TAB_SEL_L,            "graphics.IMG_TAB_SEL_L" },
199   { IMG_TAB_UNSEL,            "graphics.IMG_TAB_UNSEL" },
200   { IMG_TAB_UNSEL_L,          "graphics.IMG_TAB_UNSEL_L" },
201   { IMG_TAB_LINE,             "graphics.IMG_TAB_LINE" },
202   { IMG_TAB_LINEL,            "graphics.IMG_TAB_LINEL" },
203   { IMG_TAB_LINER,            "graphics.IMG_TAB_LINER" },
204   { IMG_ICON_MISSING,         "graphics.IMG_ICON_MISSING" },
205   { IMG_SELECTED_HILITE,      "graphics.IMG_SELECTED_HILITE" },
206   { IMG_PREVIEW_MISSING,      "graphics.IMG_PREVIEW_MISSING" },
207   { IMG_ARROW_UP,             "graphics.IMG_ARROW_UP", },
208   { IMG_ARROW_DOWN,           "graphics.IMG_ARROW_DOWN", },
209   { IMG_ARROW_SCROLLBAR,      "graphics.IMG_ARROW_SCROLLBAR", },
210   { IMG_HOURGLASS,            "graphics.IMG_HOURGLASS", },
211   { IMG_MAX,                  NULL },
212 };
213
214 unsigned char ui_imagecache ( char *basepath ) {
215   unsigned int i;
216   char fullpath [ PATH_MAX ];
217
218   // loaded
219
220   for ( i = 0; i < IMG_MAX; i++ ) {
221
222     if ( g_imagecache [ i ].id != i ) {
223       pnd_log ( pndn_error, "ERROR: Internal table mismatch during caching [%u]\n", i );
224       exit ( -1 );
225     }
226
227     char *filename = pnd_conf_get_as_char ( g_conf, g_imagecache [ i ].confname );
228
229     if ( ! filename ) {
230       pnd_log ( pndn_error, "ERROR: Missing filename in conf for key: %s\n", g_imagecache [ i ].confname );
231       return ( 0 );
232     }
233
234     if ( filename [ 0 ] == '/' ) {
235       strncpy ( fullpath, filename, PATH_MAX );
236     } else {
237       sprintf ( fullpath, "%s/%s", basepath, filename );
238     }
239
240     if ( ! ( g_imagecache [ i ].i = IMG_Load ( fullpath ) ) ) {
241       pnd_log ( pndn_error, "ERROR: Couldn't load static cache image: %s\n", fullpath );
242       return ( 0 );
243     }
244
245   } // for
246
247   // generated
248   //g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = SDL_CreateRGBSurface ( SDL_SWSURFACE, 60, 60, 32, 0xFF0000, 0x00FF00, 0xFF, 0xFF000000 );
249   //boxRGBA ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, 0, 0, 60, 60, 100, 100, 100, 250 );
250
251   // post processing
252   //
253
254   // scale icons
255   g_imagecache [ IMG_ICON_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_ICON_MISSING ].i, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ), -1 );
256   // scale text hilight
257   g_imagecache [ IMG_SELECTED_HILITE ].i = ui_scale_image ( g_imagecache [ IMG_SELECTED_HILITE ].i, pnd_conf_get_as_int_d ( g_conf, "grid.text_width", 50 ), -1 );
258   // scale preview no-pic
259   g_imagecache [ IMG_PREVIEW_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_PREVIEW_MISSING ].i,
260                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ),
261                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 50 ) );
262
263   // set alpha on detail panel
264   //SDL_SetAlpha ( g_imagecache [ IMG_DETAIL_BG ].i, SDL_SRCALPHA, pnd_conf_get_as_int_d ( g_conf, "display.detail_bg_alpha", 50 ) );
265
266   return ( 1 );
267 } // ui_imagecache
268
269 void ui_render ( void ) {
270
271   // 800x480:
272   // divide width: 550 / 250
273   // divide left side: 5 columns == 110px width
274   //   20px buffer either side == 70px wide icon + 20 + 20?
275
276   // what jobs to do during render?
277   //
278
279 #define R_BG     (1<<0)
280 #define R_TABS   (1<<1)
281 #define R_DETAIL (1<<2)
282 #define R_GRID   (1<<3)
283
284 #define R_ALL (R_BG|R_TABS|R_DETAIL|R_GRID)
285
286   unsigned int render_jobs_b = 0;
287
288   if ( render_mask & CHANGED_EVERYTHING ) {
289     render_jobs_b |= R_ALL;
290   }
291
292   if ( render_mask & CHANGED_SELECTION ) {
293     render_jobs_b |= R_GRID;
294     render_jobs_b |= R_DETAIL;
295   }
296
297   if ( render_mask & CHANGED_CATEGORY ) {
298     render_jobs_b |= R_ALL;
299   }
300
301   render_mask = CHANGED_NOTHING;
302
303   // render everything
304   //
305   unsigned int icon_rows;
306
307 #define MAXRECTS 200
308   SDL_Rect rects [ MAXRECTS ], src;
309   SDL_Rect *dest = rects;
310   bzero ( dest, sizeof(SDL_Rect)*MAXRECTS );
311
312   unsigned int row, displayrow, col;
313   mm_appref_t *appiter;
314
315   unsigned int screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
316
317   unsigned char row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 4 );
318   unsigned char col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
319
320   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
321   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
322   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
323   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
324
325   unsigned int grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
326   unsigned int grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
327
328   unsigned int icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
329   unsigned int icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
330   unsigned int icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
331   unsigned int icon_max_height = pnd_conf_get_as_int ( g_conf, "grid.icon_max_height" );
332   unsigned int sel_icon_offset_x = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_x", 0 );
333   unsigned int sel_icon_offset_y = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_y", 0 );
334
335   unsigned int text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
336   unsigned int text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
337   unsigned int text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
338   unsigned int text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
339
340   unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
341   unsigned int cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
342
343   // how many total rows do we need?
344   icon_rows = g_categories [ ui_category ].refcount / col_max;
345   if ( g_categories [ ui_category ].refcount % col_max > 0 ) {
346     icon_rows++;
347   }
348
349   // if no selected app yet, select the first one
350 #if 0
351   if ( ! ui_selected ) {
352     ui_selected = g_categories [ ui_category ].refs;
353   }
354 #endif
355
356   // reset touchscreen regions
357   ui_register_reset();
358
359   // ensure selection is visible
360   if ( ui_selected ) {
361
362     int index = ui_selected_index();
363     int topleft = col_max * ui_rows_scrolled_down;
364     int botright = ( col_max * ( ui_rows_scrolled_down + row_max ) - 1 );
365
366     pnd_log ( PND_LOG_DEFAULT, "index %u tl %u br %u\n", index, topleft, botright );
367
368     if ( index < topleft ) {
369       ui_rows_scrolled_down -= pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
370       render_jobs_b |= R_ALL;
371     } else if ( index > botright ) {
372       ui_rows_scrolled_down += pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
373       render_jobs_b |= R_ALL;
374     }
375
376     if ( ui_rows_scrolled_down < 0 ) {
377       ui_rows_scrolled_down = 0;
378     } else if ( ui_rows_scrolled_down > icon_rows ) {
379       ui_rows_scrolled_down = icon_rows;
380     }
381
382   } // ensire visible
383
384   // render background
385   if ( render_jobs_b & R_BG ) {
386
387     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
388       dest -> x = 0;
389       dest -> y = 0;
390       dest -> w = sdl_realscreen -> w;
391       dest -> h = sdl_realscreen -> h;
392       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
393       dest++;
394     }
395
396     // tabmask
397     if ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i ) {
398       dest -> x = 0;
399       dest -> y = 0;
400       dest -> w = sdl_realscreen -> w;
401       dest -> h = sdl_realscreen -> h;
402       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
403       dest++;
404     }
405
406   } // r_bg
407
408   // tabs
409   if ( g_imagecache [ IMG_TAB_SEL ].i && g_imagecache [ IMG_TAB_UNSEL ].i ) {
410     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
411     unsigned int tab_height = pnd_conf_get_as_int ( g_conf, "tabs.tab_height" );
412     unsigned int tab_selheight = pnd_conf_get_as_int ( g_conf, "tabs.tab_selheight" );
413     unsigned int tab_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_x" );
414     unsigned int tab_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_y" );
415     unsigned int text_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_x" );
416     unsigned int text_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_y" );
417     unsigned int text_width = pnd_conf_get_as_int ( g_conf, "tabs.text_width" );
418     unsigned int maxtab = ( screen_width / tab_width ) < g_categorycount ? ( screen_width / tab_width ) + ui_catshift : g_categorycount + ui_catshift;
419     unsigned int maxtabspot = ( screen_width / tab_width );
420
421     // draw tabs with categories
422     for ( col = ui_catshift;
423           col < maxtab;
424           col++ )
425     {
426
427       SDL_Surface *s;
428
429       // if this is the first (leftmost) tab, we use different artwork
430       // than if the other tabs (so skinner can link lines up nicely.)
431       if ( col == ui_catshift ) {
432         // leftmost tab, special case
433
434         if ( col == ui_category ) {
435           s = g_imagecache [ IMG_TAB_SEL_L ].i;
436         } else {
437           s = g_imagecache [ IMG_TAB_UNSEL_L ].i;
438         }
439
440       } else {
441         // normal (not leftmost) tab
442
443         if ( col == ui_category ) {
444           s = g_imagecache [ IMG_TAB_SEL ].i;
445         } else {
446           s = g_imagecache [ IMG_TAB_UNSEL ].i;
447         }
448
449       } // first col, or not first col?
450
451       // draw tab
452       src.x = 0;
453       src.y = 0;
454 #if 0
455       src.w = tab_width;
456       if ( col == ui_category ) {
457         src.h = tab_selheight;
458       } else {
459         src.h = tab_height;
460       }
461 #else
462       src.w = s -> w;
463       src.h = s -> h;
464 #endif
465       dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
466       dest -> y = tab_offset_y;
467
468       // store touch info
469       ui_register_tab ( col, dest -> x, dest -> y, tab_width, tab_height );
470
471       if ( render_jobs_b & R_TABS ) {
472         //pnd_log ( pndn_debug, "tab %u at %ux%u\n", col, dest.x, dest.y );
473         SDL_BlitSurface ( s, &src, sdl_realscreen, dest );
474         dest++;
475
476         // draw tab line
477         if ( col == ui_category ) {
478           // no line for selected tab
479         } else {
480           if ( col - ui_catshift == 0 ) {
481             s = g_imagecache [ IMG_TAB_LINEL ].i;
482           } else if ( col - ui_catshift == maxtabspot - 1 ) {
483             s = g_imagecache [ IMG_TAB_LINER ].i;
484           } else {
485             s = g_imagecache [ IMG_TAB_LINE ].i;
486           }
487           dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
488           dest -> y = tab_offset_y + tab_height;
489           SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
490           dest++;
491         }
492
493         // draw text
494         SDL_Surface *rtext;
495         SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
496         rtext = TTF_RenderText_Blended ( g_tab_font, g_categories [ col ].catname, tmpfontcolor );
497         src.x = 0;
498         src.y = 0;
499         src.w = rtext -> w < text_width ? rtext -> w : text_width;
500         src.h = rtext -> h;
501         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width ) + text_offset_x;
502         dest -> y = tab_offset_y + text_offset_y;
503         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
504         SDL_FreeSurface ( rtext );
505         dest++;
506
507       } // r_tabs
508
509     } // for
510
511     if ( render_jobs_b & R_TABS ) {
512
513       // draw tab lines under where tabs would be if we had categories
514       for ( /* foo */; col < maxtabspot; col++ ) {
515         SDL_Surface *s;
516
517         if ( col - ui_catshift == 0 ) {
518           s = g_imagecache [ IMG_TAB_LINEL ].i;
519         } else if ( col - ui_catshift == maxtabspot - 1 ) {
520           s = g_imagecache [ IMG_TAB_LINER ].i;
521         } else {
522           s = g_imagecache [ IMG_TAB_LINE ].i;
523         }
524         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
525         dest -> y = tab_offset_y + tab_height;
526         SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
527         dest++;
528
529       } // for
530
531     } // r_tabs
532
533   } // tabs
534
535   // scroll bars and arrows
536   if ( render_jobs_b & R_BG ) {
537     unsigned char show_bar = 0;
538
539     // up?
540     if ( ui_rows_scrolled_down && g_imagecache [ IMG_ARROW_UP ].i ) {
541       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
542       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
543       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_UP ].i, NULL /* whole image */, sdl_realscreen, dest );
544       dest++;
545
546       show_bar = 1;
547     }
548
549     // down?
550     if ( ui_rows_scrolled_down + row_max < icon_rows && g_imagecache [ IMG_ARROW_DOWN ].i ) {
551       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
552       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
553       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_DOWN ].i, NULL /* whole image */, sdl_realscreen, dest );
554       dest++;
555
556       show_bar = 1;
557     }
558
559     if ( show_bar ) {
560       // show scrollbar as well
561       src.x = 0;
562       src.y = 0;
563       src.w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
564       src.h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
565       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
566       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
567       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_SCROLLBAR ].i, &src /* whole image */, sdl_realscreen, dest );
568       dest++;
569     } // bar
570
571   } // r_bg, scroll bars
572
573   // render detail pane bg
574   if ( render_jobs_b & R_DETAIL ) {
575
576     if ( pnd_conf_get_as_int_d ( g_conf, "detailpane.show", 1 ) ) {
577
578       // render detail bg
579       if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
580         src.x = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
581         src.y = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
582         src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
583         src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
584         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
585         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
586         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
587         dest++;
588       }
589
590       // render detail pane
591       if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
592         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
593         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
594         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
595         dest++;
596       }
597
598     } // detailpane frame/bg
599
600   } // r_details
601
602   // anything to render?
603   if ( render_jobs_b & R_GRID ) {
604
605     // if just rendering grid, and nothing else, better clear it first
606     if ( ! ( render_jobs_b & R_BG ) ) {
607       if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
608         src.x = grid_offset_x;
609         src.y = grid_offset_y + sel_icon_offset_y;
610         src.w = col_max * cell_width;
611         src.h = row_max * cell_height;
612
613         dest -> x = grid_offset_x;
614         dest -> y = grid_offset_y + sel_icon_offset_y;
615
616         SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
617         dest++;
618
619       }
620     }
621
622     if ( g_categories [ ui_category ].refs ) {
623
624       appiter = g_categories [ ui_category ].refs;
625       row = 0;
626       displayrow = 0;
627
628       // until we run out of apps, or run out of space
629       while ( appiter != NULL ) {
630
631         for ( col = 0; col < col_max && appiter != NULL; col++ ) {
632
633           // do we even need to render it? or are we suppressing it due to rows scrolled off the top?
634           if ( row >= ui_rows_scrolled_down ) {
635
636             // selected? show hilights
637             if ( appiter == ui_selected ) {
638               SDL_Surface *s = g_imagecache [ IMG_SELECTED_ALPHAMASK ].i;
639               // icon
640               //dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x + ( ( icon_max_width - s -> w ) / 2 );
641               dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x + sel_icon_offset_x;
642               //dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y + ( ( icon_max_height - s -> h ) / 2 );
643               dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y + sel_icon_offset_y;
644               SDL_BlitSurface ( s, NULL /* all */, sdl_realscreen, dest );
645               dest++;
646               // text
647               dest -> x = grid_offset_x + ( col * cell_width ) + text_clip_x;
648               dest -> y = grid_offset_y + ( displayrow * cell_height ) + pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
649               SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_HILITE ].i, NULL /* all */, sdl_realscreen, dest );
650               dest++;
651             } // selected?
652
653             // show icon
654             mm_cache_t *ic = cache_query_icon ( appiter -> ref -> unique_id );
655             SDL_Surface *iconsurface;
656             if ( ic ) {
657               iconsurface = ic -> i;
658             } else {
659               //pnd_log ( pndn_warning, "WARNING: TBD: Need Missin-icon icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
660               iconsurface = g_imagecache [ IMG_ICON_MISSING ].i;
661             }
662             if ( iconsurface ) {
663               //pnd_log ( pndn_debug, "Got an icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
664
665               src.x = 0;
666               src.y = 0;
667               src.w = 60;
668               src.h = 60;
669               dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x + (( icon_max_width - iconsurface -> w ) / 2);
670               dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y + (( icon_max_height - iconsurface -> h ) / 2);
671
672               SDL_BlitSurface ( iconsurface, &src, sdl_realscreen, dest );
673
674               // store touch info
675               ui_register_app ( appiter, dest -> x, dest -> y, src.w, src.h );
676
677               dest++;
678
679             }
680
681             // show text
682             if ( appiter -> ref -> title_en ) {
683               SDL_Surface *rtext;
684               SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
685               rtext = TTF_RenderText_Blended ( g_grid_font, appiter -> ref -> title_en, tmpfontcolor );
686               src.x = 0;
687               src.y = 0;
688               src.w = text_width < rtext -> w ? text_width : rtext -> w;
689               src.h = rtext -> h;
690               if ( rtext -> w > text_width ) {
691                 dest -> x = grid_offset_x + ( col * cell_width ) + text_clip_x;
692               } else {
693                 dest -> x = grid_offset_x + ( col * cell_width ) + text_offset_x - ( rtext -> w / 2 );
694               }
695               dest -> y = grid_offset_y + ( displayrow * cell_height ) + text_offset_y;
696               SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
697               SDL_FreeSurface ( rtext );
698               dest++;
699             }
700
701           } // display now? or scrolled away..
702
703           // next
704           appiter = appiter -> next;
705
706         } // for column 1...X
707
708         if ( row >= ui_rows_scrolled_down ) {
709           displayrow++;
710         }
711
712         row ++;
713
714         // are we done displaying rows?
715         if ( displayrow >= row_max ) {
716           break;
717         }
718
719       } // while
720
721     } else {
722       // no apps to render?
723       pnd_log ( pndn_rem, "No applications to render?\n" );
724     } // apps to renser?
725
726   } // r_grid
727
728   // detail panel
729   if ( ui_selected && render_jobs_b & R_DETAIL ) {
730
731     unsigned int cell_offset_x = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_x" );
732     unsigned int cell_offset_y = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_y" );
733     unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "detailtext.cell_width" );
734
735     unsigned int desty = cell_offset_y;
736
737     char buffer [ 256 ];
738
739     // full name
740     if ( ui_selected -> ref -> title_en ) {
741       SDL_Surface *rtext;
742       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
743       rtext = TTF_RenderText_Blended ( g_detailtext_font, ui_selected -> ref -> title_en, tmpfontcolor );
744       src.x = 0;
745       src.y = 0;
746       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
747       src.h = rtext -> h;
748       dest -> x = cell_offset_x;
749       dest -> y = desty;
750       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
751       SDL_FreeSurface ( rtext );
752       dest++;
753       desty += src.h;
754     }
755
756     // category
757 #if 0
758     if ( ui_selected -> ref -> main_category ) {
759
760       sprintf ( buffer, "Category: %s", ui_selected -> ref -> main_category );
761
762       SDL_Surface *rtext;
763       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
764       rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, tmpfontcolor );
765       src.x = 0;
766       src.y = 0;
767       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
768       src.h = rtext -> h;
769       dest -> x = cell_offset_x;
770       dest -> y = desty;
771       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
772       SDL_FreeSurface ( rtext );
773       dest++;
774       desty += src.h;
775     }
776 #endif
777
778     // clock
779     if ( ui_selected -> ref -> clockspeed ) {
780
781       sprintf ( buffer, "CPU Clock: %s", ui_selected -> ref -> clockspeed );
782
783       SDL_Surface *rtext;
784       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
785       rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, tmpfontcolor );
786       src.x = 0;
787       src.y = 0;
788       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
789       src.h = rtext -> h;
790       dest -> x = cell_offset_x;
791       dest -> y = desty;
792       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
793       SDL_FreeSurface ( rtext );
794       dest++;
795       desty += src.h;
796     }
797
798     // show sub-app# on right side of cpu clock?
799     //if ( ui_selected -> ref -> subapp_number )
800     {
801       sprintf ( buffer, "(app#%u)", ui_selected -> ref -> subapp_number );
802
803       SDL_Surface *rtext;
804       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
805       rtext = TTF_RenderText_Blended ( g_grid_font, buffer, tmpfontcolor );
806       dest -> x = cell_offset_x + cell_width - rtext -> w;
807       dest -> y = desty - src.h;
808       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
809       SDL_FreeSurface ( rtext );
810       dest++;
811     }
812
813     // info hint
814 #if 0 // merged into hint-line
815     if ( ui_selected -> ref -> info_filename ) {
816
817       sprintf ( buffer, "Documentation - hit Y" );
818
819       SDL_Surface *rtext;
820       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
821       rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, tmpfontcolor );
822       src.x = 0;
823       src.y = 0;
824       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
825       src.h = rtext -> h;
826       dest -> x = cell_offset_x;
827       dest -> y = desty;
828       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
829       SDL_FreeSurface ( rtext );
830       dest++;
831       desty += src.h;
832     }
833 #endif
834
835     // notes
836     if ( ui_selected -> ovrh ) {
837       char *n;
838       unsigned char i;
839       char buffer [ 50 ];
840
841       desty += 5; // a touch of spacing can't hurt
842
843       for ( i = 1; i < 4; i++ ) {
844         sprintf ( buffer, "Application-%u.note-%u", ui_selected -> ref -> subapp_number, i );
845         n = pnd_conf_get_as_char ( ui_selected -> ovrh, buffer );
846
847         if ( n ) {
848           SDL_Surface *rtext;
849           SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
850           rtext = TTF_RenderText_Blended ( g_detailtext_font, n, tmpfontcolor );
851           src.x = 0;
852           src.y = 0;
853           src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
854           src.h = rtext -> h;
855           dest -> x = cell_offset_x;
856           dest -> y = desty;
857           SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
858           SDL_FreeSurface ( rtext );
859           dest++;
860           desty += rtext -> h;
861         }
862       } // for
863
864     } // r_detail -> notes
865
866     // preview pic
867     mm_cache_t *ic = cache_query_preview ( ui_selected -> ref -> unique_id );
868     SDL_Surface *previewpic;
869
870     if ( ic ) {
871       previewpic = ic -> i;
872     } else {
873       previewpic = g_imagecache [ IMG_PREVIEW_MISSING ].i;
874     }
875
876     if ( previewpic ) {
877       dest -> x = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_x", 50 ) +
878         ( ( pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ) - previewpic -> w ) / 2 );
879       dest -> y = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_y", 50 );
880       SDL_BlitSurface ( previewpic, NULL /* whole image */, sdl_realscreen, dest );
881       dest++;
882     }
883
884   } // r_detail && selected?
885
886   // extras
887   //
888
889   // battery
890   if ( render_jobs_b & R_BG ) {
891     static int last_battlevel = 0;
892     static unsigned char batterylevel = 0;
893     char buffer [ 100 ];
894
895     if ( time ( NULL ) - last_battlevel > 60 ) {
896       batterylevel = pnd_device_get_battery_gauge_perc();
897       last_battlevel = time ( NULL );
898     }
899
900     sprintf ( buffer, "Battery: %u%%", batterylevel );
901
902     SDL_Surface *rtext;
903     SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
904     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, tmpfontcolor );
905     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.battery_x", 20 );
906     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.battery_y", 450 );
907     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
908     SDL_FreeSurface ( rtext );
909     dest++;
910   }
911
912   // hints
913   if ( pnd_conf_get_as_char ( g_conf, "display.hintline" ) ) {
914     char *buffer;
915     unsigned int hintx, hinty;
916     hintx = pnd_conf_get_as_int_d ( g_conf, "display.hint_x", 40 );
917     hinty = pnd_conf_get_as_int_d ( g_conf, "display.hint_y", 450 );
918     static unsigned int lastwidth = 3000;
919
920     if ( ui_selected && ui_selected -> ref -> info_filename ) {
921       buffer = "Documentation - hit Y";
922     } else {
923       buffer = pnd_conf_get_as_char ( g_conf, "display.hintline" );
924     }
925
926     SDL_Surface *rtext;
927     SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
928     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, tmpfontcolor );
929
930     // clear bg
931     if ( ! ( render_jobs_b & R_BG ) ) {
932       src.x = hintx;
933       src.y = hinty;
934       src.w = lastwidth;
935       src.h = rtext -> h;
936       dest -> x = hintx;
937       dest -> y = hinty;
938       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, &src, sdl_realscreen, dest );
939       dest++;
940       lastwidth = rtext -> w;
941     }
942
943     // now render text
944     dest -> x = hintx;
945     dest -> y = hinty;
946     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
947     SDL_FreeSurface ( rtext );
948     dest++;
949   }
950
951   // clock time
952   if ( render_jobs_b & R_BG &&
953        pnd_conf_get_as_int_d ( g_conf, "display.clock_x", -1 ) != -1 )
954   {
955     char buffer [ 50 ];
956
957     time_t t = time ( NULL );
958     struct tm *tm = localtime ( &t );
959     strftime ( buffer, 50, "%a %H:%M %F", tm );
960
961     SDL_Surface *rtext;
962     SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
963     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, tmpfontcolor );
964     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.clock_x", 700 );
965     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.clock_y", 450 );
966     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
967     SDL_FreeSurface ( rtext );
968     dest++;
969   }
970
971   // update all the rects and send it all to sdl
972   // - at this point, we could probably just do 1 rect, of the
973   //   whole screen, and be faster :/
974   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
975
976 } // ui_render
977
978 void ui_process_input ( unsigned char block_p ) {
979   SDL_Event event;
980
981   unsigned char ui_event = 0; // if we get a ui event, flip to 1 and break
982   //static ui_sdl_button_e ui_mask = uisb_none; // current buttons down
983
984   while ( ! ui_event &&
985           block_p ? SDL_WaitEvent ( &event ) : SDL_PollEvent ( &event ) )
986   {
987
988     switch ( event.type ) {
989
990     case SDL_USEREVENT:
991       // update something
992
993       if ( event.user.code == sdl_user_ticker ) {
994
995         // timer went off, time to load something
996         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
997
998           // load the preview pics now!
999           pnd_disco_t *iter = ui_selected -> ref;
1000
1001           if ( iter -> preview_pic1 ) {
1002
1003             if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.threaded_preview", 0 ) ) {
1004               // load in bg thread, make user experience chuggy
1005
1006               g_preview_thread = SDL_CreateThread ( (void*)ui_threaded_defered_preview, iter );
1007
1008               if ( ! g_preview_thread ) {
1009                 pnd_log ( pndn_error, "ERROR: Couldn't create preview thread\n" );
1010               }
1011
1012             } else {
1013               // load it now, make user wait
1014
1015               if ( ! cache_preview ( iter, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
1016                                      pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
1017                  )
1018               {
1019                 pnd_log ( pndn_debug, "  Couldn't load preview pic: '%s' -> '%s'\n",
1020                           IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1021               }
1022
1023             } // threaded?
1024
1025           } // got a preview at all?
1026
1027           ui_event++;
1028         }
1029
1030       } else if ( event.user.code == sdl_user_finishedpreview ) {
1031
1032         // if we just finished the one we happen to be looking at, better redraw now; otherwise, if
1033         // we finished another, no big woop
1034         if ( ui_selected && event.user.data1 == ui_selected -> ref ) {
1035           ui_event++;
1036         }
1037
1038       } else if ( event.user.code == sdl_user_finishedicon ) {
1039         // redraw, so we can show the newly loaded icon
1040         ui_event++;
1041
1042       }
1043
1044       render_mask |= CHANGED_EVERYTHING;
1045
1046       break;
1047
1048 #if 0 // joystick motion
1049     case SDL_JOYAXISMOTION:
1050
1051       pnd_log ( PND_LOG_DEFAULT, "joystick axis\n" );
1052
1053         if ( event.jaxis.axis == 0 ) {
1054           // horiz
1055           if ( event.jaxis.value < 0 ) {
1056             ui_push_left ( 0 );
1057             pnd_log ( PND_LOG_DEFAULT, "joystick axis - LEFT\n" );
1058           } else if ( event.jaxis.value > 0 ) {
1059             ui_push_right ( 0 );
1060             pnd_log ( PND_LOG_DEFAULT, "joystick axis - RIGHT\n" );
1061           }
1062         } else if ( event.jaxis.axis == 1 ) {
1063           // vert
1064           if ( event.jaxis.value < 0 ) {
1065             ui_push_up();
1066           } else if ( event.jaxis.value > 0 ) {
1067             ui_push_down();
1068           }
1069         }
1070
1071         ui_event++;
1072
1073         break;
1074 #endif
1075
1076 #if 0 // joystick buttons
1077     case SDL_JOYBUTTONDOWN:
1078
1079       pnd_log ( PND_LOG_DEFAULT, "joystick button down %u\n", event.jbutton.button );
1080
1081       if ( event.jbutton.button == 0 ) { // B
1082         ui_mask |= uisb_b;
1083       } else if ( event.jbutton.button == 1 ) { // Y
1084         ui_mask |= uisb_y;
1085       } else if ( event.jbutton.button == 2 ) { // X
1086         ui_mask |= uisb_x;
1087       } else if ( event.jbutton.button == 3 ) { // A
1088         ui_mask |= uisb_a;
1089
1090       } else if ( event.jbutton.button == 4 ) { // Select
1091         ui_mask |= uisb_select;
1092       } else if ( event.jbutton.button == 5 ) { // Start
1093         ui_mask |= uisb_start;
1094
1095       } else if ( event.jbutton.button == 7 ) { // L
1096         ui_mask |= uisb_l;
1097       } else if ( event.jbutton.button == 8 ) { // R
1098         ui_mask |= uisb_r;
1099
1100       }
1101
1102       ui_event++;
1103
1104       break;
1105
1106     case SDL_JOYBUTTONUP:
1107
1108       pnd_log ( PND_LOG_DEFAULT, "joystick button up %u\n", event.jbutton.button );
1109
1110       if ( event.jbutton.button == 0 ) { // B
1111         ui_mask &= ~uisb_b;
1112         ui_push_exec();
1113       } else if ( event.jbutton.button == 1 ) { // Y
1114         ui_mask &= ~uisb_y;
1115       } else if ( event.jbutton.button == 2 ) { // X
1116         ui_mask &= ~uisb_x;
1117       } else if ( event.jbutton.button == 3 ) { // A
1118         ui_mask &= ~uisb_a;
1119
1120       } else if ( event.jbutton.button == 4 ) { // Select
1121         ui_mask &= ~uisb_select;
1122       } else if ( event.jbutton.button == 5 ) { // Start
1123         ui_mask &= ~uisb_start;
1124
1125       } else if ( event.jbutton.button == 7 ) { // L
1126         ui_mask &= ~uisb_l;
1127         ui_push_ltrigger();
1128       } else if ( event.jbutton.button == 8 ) { // R
1129         ui_mask &= ~uisb_r;
1130         ui_push_rtrigger();
1131
1132       }
1133
1134       ui_event++;
1135
1136       break;
1137 #endif
1138
1139 #if 1 // keyboard events
1140     case SDL_KEYUP:
1141
1142       //pnd_log ( pndn_debug, "key up %u\n", event.key.keysym.sym );
1143
1144       // SDLK_LALT -> Start
1145
1146       // directional
1147       if ( event.key.keysym.sym == SDLK_RIGHT ) {
1148         ui_push_right ( 0 );
1149         ui_event++;
1150       } else if ( event.key.keysym.sym == SDLK_LEFT ) {
1151         ui_push_left ( 0 );
1152         ui_event++;
1153       } else if ( event.key.keysym.sym == SDLK_UP ) {
1154         ui_push_up();
1155         ui_event++;
1156       } else if ( event.key.keysym.sym == SDLK_DOWN ) {
1157         ui_push_down();
1158         ui_event++;
1159       } else if ( event.key.keysym.sym == SDLK_SPACE || event.key.keysym.sym == SDLK_END ) {
1160         ui_push_exec();
1161         ui_event++;
1162       } else if ( event.key.keysym.sym == SDLK_z || event.key.keysym.sym == SDLK_RSHIFT ) {
1163         ui_push_ltrigger();
1164         ui_event++;
1165       } else if ( event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_RCTRL ) {
1166         ui_push_rtrigger();
1167         ui_event++;
1168       } else if ( event.key.keysym.sym == SDLK_y || event.key.keysym.sym == SDLK_PAGEUP ) {
1169         // info
1170         if ( ui_selected ) {
1171           ui_show_info ( pnd_run_script, ui_selected -> ref );
1172           ui_event++;
1173         }
1174
1175       } else if ( event.key.keysym.sym == SDLK_LALT ) { // start button
1176         ui_push_exec();
1177         ui_event++;
1178
1179       } else if ( event.key.keysym.sym == SDLK_LCTRL /*LALT*/ ) { // select button
1180         char *opts [ 20 ] = {
1181           "Return to Minimenu",
1182           "Shutdown Pandora",
1183           "Rescan for Applications",
1184           "Cache previews to SD now",
1185           "Run xfce4 from Minimenu",
1186           "Run a terminal/console",
1187           "Exit and run xfce4",
1188           "Exit and run pmenu",
1189           "Quit (<- beware)",
1190           "Select a Minimenu skin",
1191           "About Minimenu"
1192         };
1193         int sel = ui_modal_single_menu ( opts, 11, "Minimenu", "Enter to select; other to return." );
1194
1195         char buffer [ 100 ];
1196         if ( sel == 0 ) {
1197           // do nothing
1198         } else if ( sel == 1 ) {
1199           // shutdown
1200           sprintf ( buffer, "sudo poweroff" );
1201           system ( buffer );
1202         } else if ( sel == 2 ) {
1203           // rescan apps
1204           pnd_log ( pndn_debug, "Freeing up applications\n" );
1205           applications_free();
1206           pnd_log ( pndn_debug, "Rescanning applications\n" );
1207           applications_scan();
1208           // reset view
1209           ui_selected = NULL;
1210           ui_rows_scrolled_down = 0;
1211         } else if ( sel == 3 ) {
1212           // cache preview to SD now
1213           extern pnd_box_handle g_active_apps;
1214           pnd_box_handle h = g_active_apps;
1215
1216           unsigned int maxwidth, maxheight;
1217           maxwidth = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 );
1218           maxheight = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 );
1219
1220           pnd_disco_t *iter = pnd_box_get_head ( h );
1221
1222           while ( iter ) {
1223
1224             // cache it
1225             if ( ! cache_preview ( iter, maxwidth, maxheight ) ) {
1226               pnd_log ( pndn_debug, "Force cache: Couldn't load preview pic: '%s' -> '%s'\n",
1227                         IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1228             }
1229
1230             // next
1231             iter = pnd_box_get_next ( iter );
1232           } // while
1233
1234         } else if ( sel == 4 ) {
1235           // run xfce
1236           char buffer [ PATH_MAX ];
1237           sprintf ( buffer, "%s %s\n", MM_RUN, "/usr/bin/startxfce4" );
1238           emit_and_quit ( buffer );
1239         } else if ( sel == 5 ) {
1240           // run terminal
1241           char *argv[5];
1242           argv [ 0 ] = pnd_conf_get_as_char ( g_conf, "utility.terminal" );
1243           argv [ 1 ] = NULL;
1244
1245           if ( argv [ 0 ] ) {
1246             ui_forkexec ( argv );
1247           }
1248
1249         } else if ( sel == 6 ) {
1250           // set env to xfce
1251           sprintf ( buffer, "echo startxfce4 > /tmp/gui.load" );
1252           system ( buffer );
1253           emit_and_quit ( buffer );
1254           //sprintf ( buffer, "sudo poweroff" );
1255           //system ( buffer );
1256           exit ( 0 );
1257         } else if ( sel == 7 ) {
1258           // set env to pmenu
1259           sprintf ( buffer, "echo pmenu > /tmp/gui.load" );
1260           system ( buffer );
1261           emit_and_quit ( buffer );
1262           //sprintf ( buffer, "sudo poweroff" );
1263           //system ( buffer );
1264           exit ( 0 );
1265         } else if ( sel == 8 ) {
1266           emit_and_quit ( MM_QUIT );
1267         } else if ( sel == 9 ) {
1268           // select skin
1269           if ( ui_pick_skin() ) {
1270             emit_and_quit ( MM_RESTART );
1271           }
1272         } else if ( sel == 10 ) {
1273           // about
1274         }
1275
1276         ui_event++;
1277         render_mask |= CHANGED_EVERYTHING;
1278       }
1279
1280       // extras
1281       if ( event.key.keysym.sym == SDLK_q ) {
1282         emit_and_quit ( MM_QUIT );
1283       }
1284
1285       break;
1286 #endif
1287
1288 #if 1 // mouse / touchscreen
1289 #if 0
1290     case SDL_MOUSEBUTTONDOWN:
1291       if ( event.button.button == SDL_BUTTON_LEFT ) {
1292         cb_pointer_press ( gc, event.button.x / g_scale, event.button.y / g_scale );
1293         ui_event++;
1294       }
1295       break;
1296 #endif
1297
1298     case SDL_MOUSEBUTTONUP:
1299       if ( event.button.button == SDL_BUTTON_LEFT ) {
1300         ui_touch_act ( event.button.x, event.button.y );
1301         ui_event++;
1302       }
1303       break;
1304 #endif
1305
1306     case SDL_QUIT:
1307       exit ( 0 );
1308       break;
1309
1310     default:
1311       break;
1312
1313     } // switch event type
1314
1315   } // while poll
1316
1317   return;
1318 }
1319
1320 void ui_push_left ( unsigned char forcecoil ) {
1321
1322   if ( ! ui_selected ) {
1323     ui_push_right ( 0 );
1324     return;
1325   }
1326
1327   // what column we in?
1328   unsigned int col = ui_determine_screen_col ( ui_selected );
1329
1330   // are we already at first item?
1331   if ( forcecoil == 0 &&
1332        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1333        col == 0 )
1334   {
1335     unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
1336     while ( i && ui_selected -> next ) {
1337       ui_push_right ( 0 );
1338       i--;
1339     }
1340
1341   } else if ( g_categories [ ui_category ].refs == ui_selected ) {
1342     // can't go any more left, we're at the head
1343
1344   } else {
1345     // figure out the previous item; yay for singly linked list :/
1346     mm_appref_t *i = g_categories [ ui_category ].refs;
1347     while ( i ) {
1348       if ( i -> next == ui_selected ) {
1349         ui_selected = i;
1350         break;
1351       }
1352       i = i -> next;
1353     }
1354   }
1355
1356   ui_set_selected ( ui_selected );
1357
1358   return;
1359 }
1360
1361 void ui_push_right ( unsigned char forcecoil ) {
1362
1363   if ( ui_selected ) {
1364
1365     // what column we in?
1366     unsigned int col = ui_determine_screen_col ( ui_selected );
1367
1368     // wrap same or no?
1369     if ( forcecoil == 0 &&
1370          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1371          // and selected is far-right, or last icon in category (might not be far right)
1372          ( ( col == pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1 ) ||
1373            ( ui_selected -> next == NULL ) )
1374        )
1375     {
1376       // same wrap
1377       //unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
1378       while ( col /*i*/ ) {
1379         ui_push_left ( 0 );
1380         col--; //i--;
1381       }
1382
1383     } else {
1384       // just go to the next
1385
1386       if ( ui_selected -> next ) {
1387         ui_selected = ui_selected -> next;
1388       }
1389
1390     }
1391
1392   } else {
1393     ui_selected = g_categories [ ui_category ].refs;
1394   }
1395
1396   ui_set_selected ( ui_selected );
1397
1398   return;
1399 }
1400
1401 void ui_push_up ( void ) {
1402   unsigned char col_max = pnd_conf_get_as_int ( g_conf, "grid.col_max" );
1403
1404   if ( ! ui_selected ) {
1405     return;
1406   }
1407
1408   // what row we in?
1409   unsigned int row = ui_determine_row ( ui_selected );
1410
1411   if ( row == 0 &&
1412        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 1 ) == 0 )
1413   {
1414     // wrap around instead
1415
1416     unsigned int col = ui_determine_screen_col ( ui_selected );
1417
1418     // go to end
1419     ui_selected = g_categories [ ui_category ].refs;
1420     while ( ui_selected -> next ) {
1421       ui_selected = ui_selected -> next;
1422     }
1423
1424     // try to move to same column
1425     unsigned int newcol = ui_determine_screen_col ( ui_selected );
1426     if ( newcol > col ) {
1427       col = newcol - col;
1428       while ( col ) {
1429         ui_push_left ( 0 );
1430         col--;
1431       }
1432     }
1433
1434     // scroll down to show it
1435     int r = ui_determine_row ( ui_selected ) - 1;
1436     if ( r - pnd_conf_get_as_int ( g_conf, "grid.row_max" ) > 0 ) {
1437       ui_rows_scrolled_down = (unsigned int) r;
1438     }
1439
1440   } else {
1441     // stop at top/bottom
1442
1443     while ( col_max ) {
1444       ui_push_left ( 1 );
1445       col_max--;
1446     }
1447
1448   }
1449
1450   return;
1451 }
1452
1453 void ui_push_down ( void ) {
1454   unsigned char col_max = pnd_conf_get_as_int ( g_conf, "grid.col_max" );
1455
1456   if ( ui_selected ) {
1457
1458     // what row we in?
1459     unsigned int row = ui_determine_row ( ui_selected );
1460
1461     // max rows?
1462     unsigned int icon_rows = g_categories [ ui_category ].refcount / col_max;
1463     if ( g_categories [ ui_category ].refcount % col_max > 0 ) {
1464       icon_rows++;
1465     }
1466
1467     // we at the end?
1468     if ( row == ( icon_rows - 1 ) &&
1469          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 1 ) == 0 )
1470     {
1471
1472       unsigned char col = ui_determine_screen_col ( ui_selected );
1473
1474       ui_selected = g_categories [ ui_category ].refs;
1475
1476       while ( col ) {
1477         ui_selected = ui_selected -> next;
1478         col--;
1479       }
1480
1481       ui_rows_scrolled_down = 0;
1482
1483       render_mask |= CHANGED_EVERYTHING;
1484
1485     } else {
1486
1487       while ( col_max ) {
1488         ui_push_right ( 1 );
1489         col_max--;
1490       }
1491
1492     }
1493
1494   } else {
1495     ui_push_right ( 0 );
1496   }
1497
1498   return;
1499 }
1500
1501 void ui_push_exec ( void ) {
1502
1503   if ( ui_selected ) {
1504     pnd_apps_exec_disco ( pnd_run_script, ui_selected -> ref, PND_EXEC_OPTION_NORUN, NULL );
1505     char buffer [ PATH_MAX ];
1506     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1507     emit_and_quit ( buffer );
1508   }
1509
1510   return;
1511 }
1512
1513 void ui_push_ltrigger ( void ) {
1514   unsigned char oldcat = ui_category;
1515
1516   if ( ui_category > 0 ) {
1517     ui_category--;
1518   } else {
1519     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1520       ui_category = g_categorycount - 1;
1521     }
1522   }
1523
1524   if ( oldcat != ui_category ) {
1525     ui_selected = NULL;
1526     ui_set_selected ( ui_selected );
1527   }
1528
1529   // make tab visible?
1530   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
1531     ui_catshift--;
1532   }
1533
1534   // unscroll
1535   ui_rows_scrolled_down = 0;
1536
1537   render_mask |= CHANGED_CATEGORY;
1538
1539   return;
1540 }
1541
1542 void ui_push_rtrigger ( void ) {
1543   unsigned char oldcat = ui_category;
1544
1545   unsigned int screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
1546   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1547
1548   if ( ui_category < ( g_categorycount - 1 ) ) {
1549     ui_category++;
1550   } else {
1551     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1552       ui_category = 0;
1553     }
1554   }
1555
1556   if ( oldcat != ui_category ) {
1557     ui_selected = NULL;
1558     ui_set_selected ( ui_selected );
1559   }
1560
1561   // make tab visible?
1562   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
1563     ui_catshift++;
1564   }
1565
1566   // unscroll
1567   ui_rows_scrolled_down = 0;
1568
1569   render_mask |= CHANGED_CATEGORY;
1570
1571   return;
1572 }
1573
1574 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
1575   double scale = 1000000.0;
1576   double scalex = 1000000.0;
1577   double scaley = 1000000.0;
1578   SDL_Surface *scaled;
1579
1580   scalex = (double)maxwidth / (double)s -> w;
1581
1582   if ( maxheight == -1 ) {
1583     scale = scalex;
1584   } else {
1585     scaley = (double)maxheight / (double)s -> h;
1586
1587     if ( scaley < scalex ) {
1588       scale = scaley;
1589     } else {
1590       scale = scalex;
1591     }
1592
1593   }
1594
1595   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
1596   SDL_FreeSurface ( s );
1597   s = scaled;
1598
1599   return ( s );
1600 }
1601
1602 void ui_loadscreen ( void ) {
1603
1604   SDL_Rect dest;
1605
1606   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1607   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1608   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1609   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1610
1611   // clear the screen
1612   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1613
1614   // render text
1615   SDL_Surface *rtext;
1616   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1617   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", tmpfontcolor );
1618   dest.x = 20;
1619   dest.y = 20;
1620   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1621   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1622   SDL_FreeSurface ( rtext );
1623
1624   return;
1625 }
1626
1627 void ui_discoverscreen ( unsigned char clearscreen ) {
1628
1629   SDL_Rect dest;
1630
1631   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1632   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1633   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1634   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1635
1636   // clear the screen
1637   if ( clearscreen ) {
1638     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1639
1640     // render background
1641     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1642       dest.x = 0;
1643       dest.y = 0;
1644       dest.w = sdl_realscreen -> w;
1645       dest.h = sdl_realscreen -> h;
1646       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1647       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1648     }
1649
1650   }
1651
1652   // render text
1653   SDL_Surface *rtext;
1654   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1655   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", tmpfontcolor );
1656   if ( clearscreen ) {
1657     dest.x = 20;
1658     dest.y = 20;
1659   } else {
1660     dest.x = 20;
1661     dest.y = 40;
1662   }
1663   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1664   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1665   SDL_FreeSurface ( rtext );
1666
1667   // render icon
1668   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1669     dest.x = rtext -> w + 30;
1670     dest.y = 20;
1671     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
1672     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1673   }
1674
1675   return;
1676 }
1677
1678 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
1679
1680   SDL_Rect rects [ 4 ];
1681   SDL_Rect *dest = rects;
1682   SDL_Rect src;
1683   bzero ( dest, sizeof(SDL_Rect)* 4 );
1684
1685   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1686   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1687   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1688   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1689
1690   static unsigned int stepx = 0;
1691
1692   // clear the screen
1693   if ( clearscreen ) {
1694     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1695
1696     // render background
1697     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1698       dest -> x = 0;
1699       dest -> y = 0;
1700       dest -> w = sdl_realscreen -> w;
1701       dest -> h = sdl_realscreen -> h;
1702       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1703       dest++;
1704     }
1705
1706   } else {
1707
1708     // render background
1709     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1710       src.x = 0;
1711       src.y = 0;
1712       src.w = sdl_realscreen -> w;
1713       src.h = 100;
1714       dest -> x = 0;
1715       dest -> y = 0;
1716       dest -> w = sdl_realscreen -> w;
1717       dest -> h = sdl_realscreen -> h;
1718       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
1719       dest++;
1720     }
1721
1722   } // clear it
1723
1724   // render text
1725   SDL_Surface *rtext;
1726   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1727   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
1728   dest -> x = 20;
1729   dest -> y = 20;
1730   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1731   SDL_FreeSurface ( rtext );
1732   dest++;
1733
1734   // render icon
1735   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1736     dest -> x = rtext -> w + 30 + stepx;
1737     dest -> y = 20;
1738     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
1739     dest++;
1740   }
1741
1742   // filename
1743   if ( filename ) {
1744     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
1745     dest -> x = 20;
1746     dest -> y = 50;
1747     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1748     SDL_FreeSurface ( rtext );
1749     dest++;
1750   }
1751
1752   // move across
1753   stepx += 20;
1754
1755   if ( stepx > 350 ) {
1756     stepx = 0;
1757   }
1758
1759   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1760
1761   return;
1762 }
1763
1764 int ui_selected_index ( void ) {
1765
1766   if ( ! ui_selected ) {
1767     return ( -1 ); // no index
1768   }
1769
1770   mm_appref_t *r = g_categories [ ui_category ].refs;
1771   int counter = 0;
1772   while ( r ) {
1773     if ( r == ui_selected ) {
1774       return ( counter );
1775     }
1776     r = r -> next;
1777     counter++;
1778   }
1779
1780   return ( -1 );
1781 }
1782
1783 static mm_appref_t *timer_ref = NULL;
1784 void ui_set_selected ( mm_appref_t *r ) {
1785
1786   render_mask |= CHANGED_SELECTION;
1787
1788   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
1789     return; // no desire to defer anything
1790   }
1791
1792   if ( ! r ) {
1793     // cancel timer
1794     SDL_SetTimer ( 0, NULL );
1795     timer_ref = NULL;
1796     return;
1797   }
1798
1799   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
1800   timer_ref = r;
1801
1802   return;
1803 }
1804
1805 unsigned int ui_callback_f ( unsigned int t ) {
1806
1807   if ( ui_selected != timer_ref ) {
1808     return ( 0 ); // user has moved it, who cares
1809   }
1810
1811   SDL_Event e;
1812   bzero ( &e, sizeof(SDL_Event) );
1813   e.type = SDL_USEREVENT;
1814   e.user.code = sdl_user_ticker;
1815   SDL_PushEvent ( &e );
1816
1817   return ( 0 );
1818 }
1819
1820 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
1821   SDL_Rect rects [ 40 ];
1822   SDL_Rect *dest = rects;
1823   SDL_Rect src;
1824   SDL_Surface *rtext;
1825
1826   bzero ( rects, sizeof(SDL_Rect) * 40 );
1827
1828   unsigned int sel = 0;
1829
1830   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1831   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1832   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1833   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1834
1835   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1836
1837   SDL_Color selfontcolor = { 0/*font_rgba_r*/, font_rgba_g, font_rgba_b, font_rgba_a };
1838
1839   unsigned int i;
1840   SDL_Event event;
1841
1842   while ( 1 ) {
1843
1844     // clear
1845     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1846     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1847     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
1848     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
1849     SDL_FillRect( sdl_realscreen, dest, 0 );
1850
1851     // show dialog background
1852     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
1853       src.x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1854       src.y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1855       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
1856       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
1857       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1858       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1859       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
1860       // repeat for darken?
1861       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
1862       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
1863       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1864       dest++;
1865     }
1866
1867     // show dialog frame
1868     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
1869       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1870       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1871       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
1872       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1873       dest++;
1874     }
1875
1876     // show header
1877     if ( title ) {
1878       rtext = TTF_RenderText_Blended ( g_tab_font, title, tmpfontcolor );
1879       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
1880       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
1881       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1882       SDL_FreeSurface ( rtext );
1883       dest++;
1884     }
1885
1886     // show footer
1887     if ( footer ) {
1888       rtext = TTF_RenderText_Blended ( g_tab_font, footer, tmpfontcolor );
1889       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
1890       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
1891         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
1892         - 60;
1893       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1894       SDL_FreeSurface ( rtext );
1895       dest++;
1896     }
1897
1898     // show options
1899     for ( i = 0; i < argc; i++ ) {
1900
1901       // show options
1902       if ( sel == i ) {
1903         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
1904       } else {
1905         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], tmpfontcolor );
1906       }
1907       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
1908       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 ) );
1909       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1910       SDL_FreeSurface ( rtext );
1911       dest++;
1912
1913     } // for
1914
1915     // update all the rects and send it all to sdl
1916     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1917     dest = rects;
1918
1919     // check for input
1920     while ( SDL_WaitEvent ( &event ) ) {
1921
1922       switch ( event.type ) {
1923
1924       case SDL_KEYUP:
1925
1926         if ( event.key.keysym.sym == SDLK_UP ) {
1927           if ( sel ) {
1928             sel--;
1929           }
1930         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
1931           if ( sel < argc - 1 ) {
1932             sel++;
1933           }
1934
1935         } else if ( event.key.keysym.sym == SDLK_RETURN ) {
1936           return ( sel );
1937
1938         } else if ( event.key.keysym.sym == SDLK_q ) {
1939           exit ( 0 );
1940
1941         } else {
1942           return ( -1 ); // nada
1943         }
1944
1945         break;
1946
1947       } // switch
1948
1949       break;
1950     } // while
1951
1952   } // while
1953
1954   return ( -1 );
1955 }
1956
1957 unsigned char ui_determine_row ( mm_appref_t *a ) {
1958   unsigned int row = 0;
1959
1960   mm_appref_t *i = g_categories [ ui_category ].refs;
1961   while ( i != a ) {
1962     i = i -> next;
1963     row++;
1964   } // while
1965   row /= pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
1966
1967   return ( row );
1968 }
1969
1970 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
1971   return ( ui_determine_row ( a ) % pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 5 ) );
1972 }
1973
1974 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
1975   unsigned int col = 0;
1976
1977   mm_appref_t *i = g_categories [ ui_category ].refs;
1978   while ( i != a ) {
1979     i = i -> next;
1980     col++;
1981   } // while
1982   col %= pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
1983
1984   return ( col );
1985 }
1986
1987 unsigned char ui_show_info ( char *pndrun, pnd_disco_t *p ) {
1988   char *viewer, *searchpath;
1989   pnd_conf_handle desktoph;
1990
1991   // viewer
1992   searchpath = pnd_conf_query_searchpath();
1993
1994   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
1995
1996   if ( ! desktoph ) {
1997     return ( 0 );
1998   }
1999
2000   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
2001
2002   if ( ! viewer ) {
2003     return ( 0 ); // no way to view the file
2004   }
2005
2006   // etc
2007   if ( ! p -> unique_id ) {
2008     return ( 0 );
2009   }
2010
2011   if ( ! p -> info_filename ) {
2012     return ( 0 );
2013   }
2014
2015   if ( ! p -> info_name ) {
2016     return ( 0 );
2017   }
2018
2019   if ( ! pndrun ) {
2020     return ( 0 );
2021   }
2022
2023   // exec line
2024   char args [ 1001 ];
2025   char *pargs = args;
2026   if ( pnd_conf_get_as_char ( desktoph, "info.viewer_args" ) ) {
2027     snprintf ( pargs, 1001, "%s %s",
2028                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
2029   } else {
2030     pargs = NULL;
2031   }
2032
2033   char pndfile [ 1024 ];
2034   if ( p -> object_type == pnd_object_type_directory ) {
2035     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
2036     strncpy ( pndfile, p -> object_path, 1000 );
2037   } else if ( p -> object_type == pnd_object_type_pnd ) {
2038     // pnd_run.sh wants the full path and filename for the .pnd file
2039     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
2040   }
2041
2042   if ( ! pnd_apps_exec ( pndrun, pndfile, p -> unique_id, viewer, p -> startdir, pargs,
2043                          p -> clockspeed ? atoi ( p -> clockspeed ) : 0, PND_EXEC_OPTION_NORUN ) )
2044   {
2045     return ( 0 );
2046   }
2047
2048   pnd_log ( pndn_debug, "Info Exec=%s\n", pnd_apps_exec_runline() );
2049
2050   // try running it
2051   int x;
2052   if ( ( x = fork() ) < 0 ) {
2053     pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
2054     return ( 0 );
2055   }
2056
2057   if ( x == 0 ) {
2058     execl ( "/bin/sh", "/bin/sh", "-c", pnd_apps_exec_runline(), (char*)NULL );
2059     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", pnd_apps_exec_runline() );
2060     return ( 0 );
2061   }
2062
2063   return ( 1 );
2064 }
2065
2066 typedef struct {
2067   SDL_Rect r;
2068   int catnum;
2069   mm_appref_t *ref;
2070 } ui_touch_t;
2071 #define MAXTOUCH 100
2072 ui_touch_t ui_touchrects [ MAXTOUCH ];
2073 unsigned char ui_touchrect_count = 0;
2074
2075 void ui_register_reset ( void ) {
2076   bzero ( ui_touchrects, sizeof(ui_touch_t)*MAXTOUCH );
2077   ui_touchrect_count = 0;
2078   return;
2079 }
2080
2081 void ui_register_tab ( unsigned char catnum, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2082
2083   if ( ui_touchrect_count == MAXTOUCH ) {
2084     return;
2085   }
2086
2087   ui_touchrects [ ui_touchrect_count ].r.x = x;
2088   ui_touchrects [ ui_touchrect_count ].r.y = y;
2089   ui_touchrects [ ui_touchrect_count ].r.w = w;
2090   ui_touchrects [ ui_touchrect_count ].r.h = h;
2091   ui_touchrects [ ui_touchrect_count ].catnum = catnum;
2092   ui_touchrect_count++;
2093
2094   return;
2095 }
2096
2097 void ui_register_app ( mm_appref_t *app, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2098
2099   if ( ui_touchrect_count == MAXTOUCH ) {
2100     return;
2101   }
2102
2103   ui_touchrects [ ui_touchrect_count ].r.x = x;
2104   ui_touchrects [ ui_touchrect_count ].r.y = y;
2105   ui_touchrects [ ui_touchrect_count ].r.w = w;
2106   ui_touchrects [ ui_touchrect_count ].r.h = h;
2107   ui_touchrects [ ui_touchrect_count ].ref = app;
2108   ui_touchrect_count++;
2109
2110   return;
2111 }
2112
2113 void ui_touch_act ( unsigned int x, unsigned int y ) {
2114
2115   unsigned char i;
2116   ui_touch_t *t;
2117
2118   for ( i = 0; i < ui_touchrect_count; i++ ) {
2119     t = &(ui_touchrects [ i ]);
2120
2121     if ( x >= t -> r.x &&
2122          x <= t -> r.x + t -> r.w &&
2123          y >= t -> r.y &&
2124          y <= t -> r.y + t -> r.h
2125        )
2126     {
2127
2128       if ( t -> ref ) {
2129         ui_selected = t -> ref;
2130         ui_push_exec();
2131       } else {
2132         if ( ui_category != t -> catnum ) {
2133           ui_selected = NULL;
2134         }
2135         ui_category = t -> catnum;
2136         render_mask |= CHANGED_CATEGORY;
2137       }
2138
2139       break;
2140     }
2141
2142   } // for
2143
2144   return;
2145 }
2146
2147 unsigned char ui_forkexec ( char *argv[] ) {
2148   char *fooby = argv[0];
2149   int x;
2150
2151   if ( ( x = fork() ) < 0 ) {
2152     pnd_log ( pndn_error, "ERROR: Couldn't fork() for '%s'\n", fooby );
2153     return ( 0 );
2154   }
2155
2156   if ( x == 0 ) { // child
2157     execv ( fooby, argv );
2158     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", fooby );
2159     return ( 0 );
2160   }
2161
2162   // parent, success
2163   return ( 1 );
2164 }
2165
2166 unsigned char ui_threaded_defered_preview ( pnd_disco_t *p ) {
2167
2168   if ( ! cache_preview ( p, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
2169                          pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
2170      )
2171   {
2172     pnd_log ( pndn_debug, "THREAD: Couldn't load preview pic: '%s' -> '%s'\n",
2173               IFNULL(p->title_en,"No Name"), p -> preview_pic1 );
2174   }
2175
2176   // trigger that we completed
2177   SDL_Event e;
2178   bzero ( &e, sizeof(SDL_Event) );
2179   e.type = SDL_USEREVENT;
2180   e.user.code = sdl_user_finishedpreview;
2181   e.user.data1 = p;
2182   SDL_PushEvent ( &e );
2183
2184   return ( 0 );
2185 }
2186
2187 SDL_Thread *g_icon_thread = NULL;
2188 void ui_post_scan ( void ) {
2189
2190   // if deferred icon load, kick off the thread now
2191   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) == 1 ) {
2192
2193     g_icon_thread = SDL_CreateThread ( (void*)ui_threaded_defered_icon, NULL );
2194
2195     if ( ! g_icon_thread ) {
2196       pnd_log ( pndn_error, "ERROR: Couldn't create icon thread\n" );
2197     }
2198
2199   } // deferred icon load
2200
2201   return;
2202 }
2203
2204 unsigned char ui_threaded_defered_icon ( void *p ) {
2205   extern pnd_box_handle g_active_apps;
2206   pnd_box_handle h = g_active_apps;
2207
2208   unsigned char maxwidth, maxheight;
2209   maxwidth = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 );
2210   maxheight = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 );
2211
2212   pnd_disco_t *iter = pnd_box_get_head ( h );
2213
2214   while ( iter ) {
2215
2216     // cache it
2217     if ( iter -> pnd_icon_pos &&
2218          ! cache_icon ( iter, maxwidth, maxheight ) )
2219     {
2220       pnd_log ( pndn_warning, "  Couldn't load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2221     } else {
2222
2223       // trigger that we completed
2224       SDL_Event e;
2225       bzero ( &e, sizeof(SDL_Event) );
2226       e.type = SDL_USEREVENT;
2227       e.user.code = sdl_user_finishedicon;
2228       SDL_PushEvent ( &e );
2229
2230       //pnd_log ( pndn_warning, "  Finished deferred load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2231       usleep ( pnd_conf_get_as_int_d ( g_conf, "minimenu.defer_icon_us", 50000 ) );
2232
2233     }
2234
2235     // next
2236     iter = pnd_box_get_next ( iter );
2237   } // while
2238
2239   return ( 0 );
2240 }
2241
2242 void ui_show_hourglass ( unsigned char updaterect ) {
2243
2244   SDL_Rect dest;
2245   SDL_Surface *s = g_imagecache [ IMG_HOURGLASS ].i;
2246
2247   dest.x = ( 800 - s -> w ) / 2;
2248   dest.y = ( 480 - s -> h ) / 2;
2249
2250   SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, &dest );
2251
2252   if ( updaterect ) {
2253     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2254   }
2255
2256   return;
2257 }
2258
2259 unsigned char ui_pick_skin ( void ) {
2260 #define MAXSKINS 10
2261   char *skins [ MAXSKINS ];
2262   unsigned char iter;
2263
2264   char *searchpath = pnd_conf_get_as_char ( g_conf, "minimenu.skin_searchpath" );
2265   char tempname [ 100 ];
2266
2267   iter = 0;
2268
2269   skins [ iter++ ] = "No skin change";
2270
2271   SEARCHPATH_PRE
2272   {
2273     DIR *d = opendir ( buffer );
2274
2275     if ( d ) {
2276       struct dirent *dd;
2277
2278       while ( ( dd = readdir ( d ) ) ) {
2279
2280         if ( dd -> d_name [ 0 ] == '.' ) {
2281           // ignore
2282         } else if ( ( dd -> d_type == DT_DIR || dd -> d_type == DT_UNKNOWN ) &&
2283                     iter < MAXSKINS )
2284         {
2285           snprintf ( tempname, 100, "Skin: %s", dd -> d_name );
2286           skins [ iter++ ] = strdup ( tempname );
2287         }
2288
2289       }
2290
2291       closedir ( d );
2292     }
2293
2294   }
2295   SEARCHPATH_POST
2296
2297   int sel = ui_modal_single_menu ( skins, iter, "Skins", "Enter to select; other to return." );
2298
2299   // did they pick one?
2300   if ( sel > 0 ) {
2301     FILE *f;
2302
2303     char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
2304     s = pnd_expand_tilde ( s );
2305
2306     f = fopen ( s, "w" );
2307
2308     free ( s );
2309
2310     if ( f ) {
2311       fprintf ( f, "%s\n", skins [ sel ] + 6 );
2312       fclose ( f );
2313     }
2314
2315     return ( 1 );
2316   }
2317
2318   return ( 0 );
2319 }