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