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