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