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