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