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