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