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